1
0
Fork 0
mirror of https://github.com/s-frick/effigenix.git synced 2026-03-28 17:49:57 +01:00

feat(cli): Stammdaten-TUI mit Master Data API-Anbindung

- Neue Screens: Kategorien, Lieferanten, Artikel, Kunden (jeweils
  Liste, Detail, Anlegen + Detailaktionen wie Bewertung, Zertifikate,
  Verkaufseinheiten, Lieferadressen, Präferenzen)
- API-Client: Resources für alle 4 Stammdaten-Aggregate implementiert
  (categories, suppliers, articles, customers) mit Mapping von
  verschachtelten Domain-VOs auf flache DTOs
- Lieferant, Artikel, Kategorie: echte HTTP-Calls gegen Backend
  (/api/suppliers, /api/articles, /api/categories, /api/customers)
- 204-No-Content-Endpoints (removeSalesUnit, removeSupplier,
  removeCertificate, removeDeliveryAddress, removeFrameContract)
  lösen Re-Fetch des Aggregats aus
- MasterdataMenu, Navigation-Erweiterung, App.tsx-Routing
This commit is contained in:
Sebastian Frick 2026-02-18 13:35:20 +01:00
parent 797f435a49
commit d27dbaa843
30 changed files with 3882 additions and 1 deletions

View file

@ -0,0 +1,183 @@
import { useState, useCallback } from 'react';
import type {
ArticleDTO,
CreateArticleRequest,
UpdateArticleRequest,
AddSalesUnitRequest,
UpdateSalesUnitPriceRequest,
} from '@effigenix/api-client';
import { client } from '../utils/api-client.js';
interface ArticlesState {
articles: ArticleDTO[];
loading: boolean;
error: string | null;
}
function errorMessage(err: unknown): string {
return err instanceof Error ? err.message : 'Unbekannter Fehler';
}
export function useArticles() {
const [state, setState] = useState<ArticlesState>({
articles: [],
loading: false,
error: null,
});
const fetchArticles = useCallback(async () => {
setState((s) => ({ ...s, loading: true, error: null }));
try {
const articles = await client.articles.list();
setState({ articles, loading: false, error: null });
} catch (err) {
setState((s) => ({ ...s, loading: false, error: errorMessage(err) }));
}
}, []);
const createArticle = useCallback(async (request: CreateArticleRequest) => {
setState((s) => ({ ...s, loading: true, error: null }));
try {
const article = await client.articles.create(request);
setState((s) => ({ articles: [...s.articles, article], loading: false, error: null }));
return article;
} catch (err) {
setState((s) => ({ ...s, loading: false, error: errorMessage(err) }));
return null;
}
}, []);
const updateArticle = useCallback(async (id: string, request: UpdateArticleRequest) => {
try {
const updated = await client.articles.update(id, request);
setState((s) => ({
...s,
articles: s.articles.map((a) => (a.id === id ? updated : a)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const activateArticle = useCallback(async (id: string) => {
try {
const updated = await client.articles.activate(id);
setState((s) => ({
...s,
articles: s.articles.map((a) => (a.id === id ? updated : a)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const deactivateArticle = useCallback(async (id: string) => {
try {
const updated = await client.articles.deactivate(id);
setState((s) => ({
...s,
articles: s.articles.map((a) => (a.id === id ? updated : a)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const addSalesUnit = useCallback(async (id: string, request: AddSalesUnitRequest) => {
try {
const updated = await client.articles.addSalesUnit(id, request);
setState((s) => ({
...s,
articles: s.articles.map((a) => (a.id === id ? updated : a)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const removeSalesUnit = useCallback(async (articleId: string, salesUnitId: string) => {
try {
const updated = await client.articles.removeSalesUnit(articleId, salesUnitId);
setState((s) => ({
...s,
articles: s.articles.map((a) => (a.id === articleId ? updated : a)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const updateSalesUnitPrice = useCallback(
async (articleId: string, salesUnitId: string, request: UpdateSalesUnitPriceRequest) => {
try {
const updated = await client.articles.updateSalesUnitPrice(articleId, salesUnitId, request);
setState((s) => ({
...s,
articles: s.articles.map((a) => (a.id === articleId ? updated : a)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
},
[],
);
const assignSupplier = useCallback(async (articleId: string, supplierId: string) => {
try {
const updated = await client.articles.assignSupplier(articleId, supplierId);
setState((s) => ({
...s,
articles: s.articles.map((a) => (a.id === articleId ? updated : a)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const removeSupplier = useCallback(async (articleId: string, supplierId: string) => {
try {
const updated = await client.articles.removeSupplier(articleId, supplierId);
setState((s) => ({
...s,
articles: s.articles.map((a) => (a.id === articleId ? updated : a)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const clearError = useCallback(() => {
setState((s) => ({ ...s, error: null }));
}, []);
return {
...state,
fetchArticles,
createArticle,
updateArticle,
activateArticle,
deactivateArticle,
addSalesUnit,
removeSalesUnit,
updateSalesUnitPrice,
assignSupplier,
removeSupplier,
clearError,
};
}

View file

@ -0,0 +1,88 @@
import { useState, useCallback } from 'react';
import type { ProductCategoryDTO } from '@effigenix/api-client';
import { client } from '../utils/api-client.js';
interface CategoriesState {
categories: ProductCategoryDTO[];
loading: boolean;
error: string | null;
}
function errorMessage(err: unknown): string {
return err instanceof Error ? err.message : 'Unbekannter Fehler';
}
export function useCategories() {
const [state, setState] = useState<CategoriesState>({
categories: [],
loading: false,
error: null,
});
const fetchCategories = useCallback(async () => {
setState((s) => ({ ...s, loading: true, error: null }));
try {
const categories = await client.categories.list();
setState({ categories, loading: false, error: null });
} catch (err) {
setState((s) => ({ ...s, loading: false, error: errorMessage(err) }));
}
}, []);
const createCategory = useCallback(async (name: string, description?: string) => {
setState((s) => ({ ...s, loading: true, error: null }));
try {
const req = description ? { name, description } : { name };
const cat = await client.categories.create(req);
setState((s) => ({ categories: [...s.categories, cat], loading: false, error: null }));
return cat;
} catch (err) {
setState((s) => ({ ...s, loading: false, error: errorMessage(err) }));
return null;
}
}, []);
const updateCategory = useCallback(
async (id: string, name: string, description: string | null) => {
try {
const updated = await client.categories.update(id, { name, description });
setState((s) => ({
...s,
categories: s.categories.map((c) => (c.id === id ? updated : c)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
},
[],
);
const deleteCategory = useCallback(async (id: string) => {
try {
await client.categories.delete(id);
setState((s) => ({
...s,
categories: s.categories.filter((c) => c.id !== id),
}));
return true;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return false;
}
}, []);
const clearError = useCallback(() => {
setState((s) => ({ ...s, error: null }));
}, []);
return {
...state,
fetchCategories,
createCategory,
updateCategory,
deleteCategory,
clearError,
};
}

View file

@ -0,0 +1,150 @@
import { useState, useCallback } from 'react';
import type {
CustomerDTO,
CustomerPreference,
CreateCustomerRequest,
UpdateCustomerRequest,
AddDeliveryAddressRequest,
} from '@effigenix/api-client';
import { client } from '../utils/api-client.js';
interface CustomersState {
customers: CustomerDTO[];
loading: boolean;
error: string | null;
}
function errorMessage(err: unknown): string {
return err instanceof Error ? err.message : 'Unbekannter Fehler';
}
export function useCustomers() {
const [state, setState] = useState<CustomersState>({
customers: [],
loading: false,
error: null,
});
const fetchCustomers = useCallback(async () => {
setState((s) => ({ ...s, loading: true, error: null }));
try {
const customers = await client.customers.list();
setState({ customers, loading: false, error: null });
} catch (err) {
setState((s) => ({ ...s, loading: false, error: errorMessage(err) }));
}
}, []);
const createCustomer = useCallback(async (request: CreateCustomerRequest) => {
setState((s) => ({ ...s, loading: true, error: null }));
try {
const customer = await client.customers.create(request);
setState((s) => ({ customers: [...s.customers, customer], loading: false, error: null }));
return customer;
} catch (err) {
setState((s) => ({ ...s, loading: false, error: errorMessage(err) }));
return null;
}
}, []);
const updateCustomer = useCallback(async (id: string, request: UpdateCustomerRequest) => {
try {
const updated = await client.customers.update(id, request);
setState((s) => ({
...s,
customers: s.customers.map((c) => (c.id === id ? updated : c)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const activateCustomer = useCallback(async (id: string) => {
try {
const updated = await client.customers.activate(id);
setState((s) => ({
...s,
customers: s.customers.map((c) => (c.id === id ? updated : c)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const deactivateCustomer = useCallback(async (id: string) => {
try {
const updated = await client.customers.deactivate(id);
setState((s) => ({
...s,
customers: s.customers.map((c) => (c.id === id ? updated : c)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const addDeliveryAddress = useCallback(async (id: string, request: AddDeliveryAddressRequest) => {
try {
const updated = await client.customers.addDeliveryAddress(id, request);
setState((s) => ({
...s,
customers: s.customers.map((c) => (c.id === id ? updated : c)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const removeDeliveryAddress = useCallback(async (id: string, label: string) => {
try {
const updated = await client.customers.removeDeliveryAddress(id, label);
setState((s) => ({
...s,
customers: s.customers.map((c) => (c.id === id ? updated : c)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const setPreferences = useCallback(async (id: string, preferences: CustomerPreference[]) => {
try {
const updated = await client.customers.setPreferences(id, preferences);
setState((s) => ({
...s,
customers: s.customers.map((c) => (c.id === id ? updated : c)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const clearError = useCallback(() => {
setState((s) => ({ ...s, error: null }));
}, []);
return {
...state,
fetchCustomers,
createCustomer,
updateCustomer,
activateCustomer,
deactivateCustomer,
addDeliveryAddress,
removeDeliveryAddress,
setPreferences,
clearError,
};
}

View file

@ -0,0 +1,151 @@
import { useState, useCallback } from 'react';
import type {
SupplierDTO,
CreateSupplierRequest,
UpdateSupplierRequest,
RateSupplierRequest,
AddCertificateRequest,
RemoveCertificateRequest,
} from '@effigenix/api-client';
import { client } from '../utils/api-client.js';
interface SuppliersState {
suppliers: SupplierDTO[];
loading: boolean;
error: string | null;
}
function errorMessage(err: unknown): string {
return err instanceof Error ? err.message : 'Unbekannter Fehler';
}
export function useSuppliers() {
const [state, setState] = useState<SuppliersState>({
suppliers: [],
loading: false,
error: null,
});
const fetchSuppliers = useCallback(async () => {
setState((s) => ({ ...s, loading: true, error: null }));
try {
const suppliers = await client.suppliers.list();
setState({ suppliers, loading: false, error: null });
} catch (err) {
setState((s) => ({ ...s, loading: false, error: errorMessage(err) }));
}
}, []);
const createSupplier = useCallback(async (request: CreateSupplierRequest) => {
setState((s) => ({ ...s, loading: true, error: null }));
try {
const supplier = await client.suppliers.create(request);
setState((s) => ({ suppliers: [...s.suppliers, supplier], loading: false, error: null }));
return supplier;
} catch (err) {
setState((s) => ({ ...s, loading: false, error: errorMessage(err) }));
return null;
}
}, []);
const updateSupplier = useCallback(async (id: string, request: UpdateSupplierRequest) => {
try {
const updated = await client.suppliers.update(id, request);
setState((s) => ({
...s,
suppliers: s.suppliers.map((sup) => (sup.id === id ? updated : sup)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const activateSupplier = useCallback(async (id: string) => {
try {
const updated = await client.suppliers.activate(id);
setState((s) => ({
...s,
suppliers: s.suppliers.map((sup) => (sup.id === id ? updated : sup)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const deactivateSupplier = useCallback(async (id: string) => {
try {
const updated = await client.suppliers.deactivate(id);
setState((s) => ({
...s,
suppliers: s.suppliers.map((sup) => (sup.id === id ? updated : sup)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const rateSupplier = useCallback(async (id: string, request: RateSupplierRequest) => {
try {
const updated = await client.suppliers.rate(id, request);
setState((s) => ({
...s,
suppliers: s.suppliers.map((sup) => (sup.id === id ? updated : sup)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const addCertificate = useCallback(async (id: string, request: AddCertificateRequest) => {
try {
const updated = await client.suppliers.addCertificate(id, request);
setState((s) => ({
...s,
suppliers: s.suppliers.map((sup) => (sup.id === id ? updated : sup)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const removeCertificate = useCallback(async (id: string, request: RemoveCertificateRequest) => {
try {
const updated = await client.suppliers.removeCertificate(id, request);
setState((s) => ({
...s,
suppliers: s.suppliers.map((sup) => (sup.id === id ? updated : sup)),
}));
return updated;
} catch (err) {
setState((s) => ({ ...s, error: errorMessage(err) }));
return null;
}
}, []);
const clearError = useCallback(() => {
setState((s) => ({ ...s, error: null }));
}, []);
return {
...state,
fetchSuppliers,
createSupplier,
updateSupplier,
activateSupplier,
deactivateSupplier,
rateSupplier,
addCertificate,
removeCertificate,
clearError,
};
}