mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 08:29:36 +01:00
fix(tui): TypeScript-Fehler durch strikte generierte OpenAPI-Typen beheben
RoleDTO auf generierten Typ umgestellt, exactOptionalPropertyTypes-Konflikte gelöst, Null-Checks für nullable AddressResponse ergänzt und Enum-Casts für string-basierte SalesUnit-Felder hinzugefügt.
This commit is contained in:
parent
7d721f9ef0
commit
c89ee359d1
9 changed files with 31 additions and 26 deletions
|
|
@ -12,8 +12,8 @@ vi.mock('../../utils/api-client.js', () => ({
|
||||||
const { client } = await import('../../utils/api-client.js');
|
const { client } = await import('../../utils/api-client.js');
|
||||||
|
|
||||||
const mockRoles: RoleDTO[] = [
|
const mockRoles: RoleDTO[] = [
|
||||||
{ id: '1', name: 'ADMIN', permissions: [] },
|
{ id: '1', name: 'ADMIN', permissions: [], description: 'Administrator' },
|
||||||
{ id: '2', name: 'USER', permissions: [] },
|
{ id: '2', name: 'SALES_MANAGER', permissions: [], description: 'Sales Manager' },
|
||||||
];
|
];
|
||||||
|
|
||||||
describe('useRoles – api-client integration', () => {
|
describe('useRoles – api-client integration', () => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Box, Text, useInput } from 'ink';
|
import { Box, Text, useInput } from 'ink';
|
||||||
import type { ArticleDTO, SalesUnitDTO } from '@effigenix/api-client';
|
import type { ArticleDTO, SalesUnitDTO, Unit, PriceModel } from '@effigenix/api-client';
|
||||||
import { UNIT_LABELS, PRICE_MODEL_LABELS } from '@effigenix/api-client';
|
import { UNIT_LABELS, PRICE_MODEL_LABELS } from '@effigenix/api-client';
|
||||||
import { useNavigation } from '../../../state/navigation-context.js';
|
import { useNavigation } from '../../../state/navigation-context.js';
|
||||||
import { useArticles } from '../../../hooks/useArticles.js';
|
import { useArticles } from '../../../hooks/useArticles.js';
|
||||||
|
|
@ -114,7 +114,7 @@ export function ArticleDetailScreen() {
|
||||||
setActionLoading(false);
|
setActionLoading(false);
|
||||||
if (updated) {
|
if (updated) {
|
||||||
setArticle(updated);
|
setArticle(updated);
|
||||||
setSuccessMessage(`Verkaufseinheit "${UNIT_LABELS[su.unit]}" entfernt.`);
|
setSuccessMessage(`Verkaufseinheit "${UNIT_LABELS[su.unit as Unit]}" entfernt.`);
|
||||||
}
|
}
|
||||||
}, [article, removeSalesUnit]);
|
}, [article, removeSalesUnit]);
|
||||||
|
|
||||||
|
|
@ -150,8 +150,8 @@ export function ArticleDetailScreen() {
|
||||||
{article.salesUnits.map((su) => (
|
{article.salesUnits.map((su) => (
|
||||||
<Box key={su.id} paddingLeft={2} gap={1}>
|
<Box key={su.id} paddingLeft={2} gap={1}>
|
||||||
<Text color="yellow">•</Text>
|
<Text color="yellow">•</Text>
|
||||||
<Text>{UNIT_LABELS[su.unit]}</Text>
|
<Text>{UNIT_LABELS[su.unit as Unit]}</Text>
|
||||||
<Text color="gray">({PRICE_MODEL_LABELS[su.priceModel]})</Text>
|
<Text color="gray">({PRICE_MODEL_LABELS[su.priceModel as PriceModel]})</Text>
|
||||||
<Text color="green">{su.price.toFixed(2)} €</Text>
|
<Text color="green">{su.price.toFixed(2)} €</Text>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
|
|
@ -181,7 +181,7 @@ export function ArticleDetailScreen() {
|
||||||
<Box key={su.id}>
|
<Box key={su.id}>
|
||||||
<Text color={i === selectedSuIndex ? 'cyan' : 'white'}>
|
<Text color={i === selectedSuIndex ? 'cyan' : 'white'}>
|
||||||
{i === selectedSuIndex ? '▶ ' : ' '}
|
{i === selectedSuIndex ? '▶ ' : ' '}
|
||||||
{UNIT_LABELS[su.unit]} – {su.price.toFixed(2)} €
|
{UNIT_LABELS[su.unit as Unit]} – {su.price.toFixed(2)} €
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Box, Text, useInput } from 'ink';
|
import { Box, Text, useInput } from 'ink';
|
||||||
import type { CustomerDTO } from '@effigenix/api-client';
|
import type { CustomerDTO, CustomerPreference } from '@effigenix/api-client';
|
||||||
import { CUSTOMER_PREFERENCE_LABELS } from '@effigenix/api-client';
|
import { CUSTOMER_PREFERENCE_LABELS } from '@effigenix/api-client';
|
||||||
import { useNavigation } from '../../../state/navigation-context.js';
|
import { useNavigation } from '../../../state/navigation-context.js';
|
||||||
import { useCustomers } from '../../../hooks/useCustomers.js';
|
import { useCustomers } from '../../../hooks/useCustomers.js';
|
||||||
|
|
@ -137,6 +137,8 @@ export function CustomerDetailScreen() {
|
||||||
if (!customer) return <Text color="red">Kunde nicht gefunden.</Text>;
|
if (!customer) return <Text color="red">Kunde nicht gefunden.</Text>;
|
||||||
|
|
||||||
const statusColor = customer.status === 'ACTIVE' ? 'green' : 'red';
|
const statusColor = customer.status === 'ACTIVE' ? 'green' : 'red';
|
||||||
|
const ba = customer.billingAddress;
|
||||||
|
const billingText = ba ? `${ba.street} ${ba.houseNumber}, ${ba.postalCode} ${ba.city}, ${ba.country}` : '–';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column" gap={1}>
|
<Box flexDirection="column" gap={1}>
|
||||||
|
|
@ -164,7 +166,7 @@ export function CustomerDetailScreen() {
|
||||||
)}
|
)}
|
||||||
<Box gap={2}>
|
<Box gap={2}>
|
||||||
<Text color="gray">Rechnungsadresse:</Text>
|
<Text color="gray">Rechnungsadresse:</Text>
|
||||||
<Text>{`${customer.billingAddress.street} ${customer.billingAddress.houseNumber}, ${customer.billingAddress.postalCode} ${customer.billingAddress.city}, ${customer.billingAddress.country}`}</Text>
|
<Text>{billingText}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
{customer.paymentTerms && (
|
{customer.paymentTerms && (
|
||||||
<Box gap={2}>
|
<Box gap={2}>
|
||||||
|
|
@ -175,7 +177,7 @@ export function CustomerDetailScreen() {
|
||||||
{customer.preferences.length > 0 && (
|
{customer.preferences.length > 0 && (
|
||||||
<Box gap={2}>
|
<Box gap={2}>
|
||||||
<Text color="gray">Präferenzen:</Text>
|
<Text color="gray">Präferenzen:</Text>
|
||||||
<Text>{customer.preferences.map((p) => CUSTOMER_PREFERENCE_LABELS[p]).join(', ')}</Text>
|
<Text>{customer.preferences.map((p) => CUSTOMER_PREFERENCE_LABELS[p as CustomerPreference]).join(', ')}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{customer.deliveryAddresses.length > 0 && (
|
{customer.deliveryAddresses.length > 0 && (
|
||||||
|
|
@ -185,7 +187,7 @@ export function CustomerDetailScreen() {
|
||||||
<Box key={addr.label} paddingLeft={2} gap={1}>
|
<Box key={addr.label} paddingLeft={2} gap={1}>
|
||||||
<Text color="yellow">•</Text>
|
<Text color="yellow">•</Text>
|
||||||
<Text bold>{addr.label}:</Text>
|
<Text bold>{addr.label}:</Text>
|
||||||
<Text>{`${addr.address.street} ${addr.address.houseNumber}, ${addr.address.city}, ${addr.address.country}`}</Text>
|
<Text>{addr.address ? `${addr.address.street} ${addr.address.houseNumber}, ${addr.address.city}, ${addr.address.country}` : '–'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
@ -217,7 +219,7 @@ export function CustomerDetailScreen() {
|
||||||
{customer.deliveryAddresses.map((addr, i) => (
|
{customer.deliveryAddresses.map((addr, i) => (
|
||||||
<Box key={addr.label}>
|
<Box key={addr.label}>
|
||||||
<Text color={i === selectedAddrIndex ? 'cyan' : 'white'}>
|
<Text color={i === selectedAddrIndex ? 'cyan' : 'white'}>
|
||||||
{i === selectedAddrIndex ? '▶ ' : ' '}{addr.label}: {addr.address.city}
|
{i === selectedAddrIndex ? '▶ ' : ' '}{addr.label}: {addr.address?.city ?? '–'}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export function SetPreferencesScreen() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
client.customers.getById(customerId)
|
client.customers.getById(customerId)
|
||||||
.then((c) => { setChecked(new Set(c.preferences)); setInitLoading(false); })
|
.then((c) => { setChecked(new Set(c.preferences as CustomerPreference[])); setInitLoading(false); })
|
||||||
.catch((err: unknown) => { setInitError(errorMessage(err)); setInitLoading(false); });
|
.catch((err: unknown) => { setInitError(errorMessage(err)); setInitLoading(false); });
|
||||||
}, [customerId]);
|
}, [customerId]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import type { SupplierStatus } from '@effigenix/api-client';
|
||||||
|
|
||||||
type Filter = 'ALL' | SupplierStatus;
|
type Filter = 'ALL' | SupplierStatus;
|
||||||
|
|
||||||
function avgRating(rating: { qualityScore: number; deliveryScore: number; priceScore: number } | null): string {
|
function avgRating(rating: { qualityScore: number; deliveryScore: number; priceScore: number } | null | undefined): string {
|
||||||
if (!rating) return '–';
|
if (!rating) return '–';
|
||||||
const avg = (rating.qualityScore + rating.deliveryScore + rating.priceScore) / 3;
|
const avg = (rating.qualityScore + rating.deliveryScore + rating.priceScore) / 3;
|
||||||
return avg.toFixed(1);
|
return avg.toFixed(1);
|
||||||
|
|
|
||||||
|
|
@ -82,8 +82,9 @@ export function ProductionOrderCreateScreen() {
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
if (_input.toLowerCase() === 'f' && productionOrder?.id) {
|
if (_input.toLowerCase() === 'f' && productionOrder?.id) {
|
||||||
|
const orderId = productionOrder.id;
|
||||||
void (async () => {
|
void (async () => {
|
||||||
const result = await releaseProductionOrder(productionOrder.id);
|
const result = await releaseProductionOrder(orderId);
|
||||||
if (result) setReleased(true);
|
if (result) setReleased(true);
|
||||||
})();
|
})();
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,9 @@ export function useCategories() {
|
||||||
const createCategory = useCallback(async (name: string, description?: string) => {
|
const createCategory = useCallback(async (name: string, description?: string) => {
|
||||||
setState((s) => ({ ...s, loading: true, error: null }));
|
setState((s) => ({ ...s, loading: true, error: null }));
|
||||||
try {
|
try {
|
||||||
const req = description ? { name, description } : { name };
|
const req: Record<string, string> = { name };
|
||||||
const cat = await client.categories.create(req);
|
if (description) req.description = description;
|
||||||
|
const cat = await client.categories.create(req as { name: string; description?: string });
|
||||||
setState((s) => ({ categories: [...s.categories, cat], loading: false, error: null }));
|
setState((s) => ({ categories: [...s.categories, cat], loading: false, error: null }));
|
||||||
return cat;
|
return cat;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -45,7 +46,9 @@ export function useCategories() {
|
||||||
const updateCategory = useCallback(
|
const updateCategory = useCallback(
|
||||||
async (id: string, name: string, description: string | null) => {
|
async (id: string, name: string, description: string | null) => {
|
||||||
try {
|
try {
|
||||||
const updated = await client.categories.update(id, { name, description });
|
const req: Record<string, string> = { name };
|
||||||
|
if (description !== null) req.description = description;
|
||||||
|
const updated = await client.categories.update(id, req as { name: string; description?: string });
|
||||||
setState((s) => ({
|
setState((s) => ({
|
||||||
...s,
|
...s,
|
||||||
categories: s.categories.map((c) => (c.id === id ? updated : c)),
|
categories: s.categories.map((c) => (c.id === id ? updated : c)),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import type { UserDTO } from '@effigenix/api-client';
|
import type { UserDTO, CreateUserRequest } from '@effigenix/api-client';
|
||||||
import { client } from '../utils/api-client.js';
|
import { client } from '../utils/api-client.js';
|
||||||
|
|
||||||
|
type RoleName = CreateUserRequest['roleNames'][number];
|
||||||
|
|
||||||
interface UsersState {
|
interface UsersState {
|
||||||
users: UserDTO[];
|
users: UserDTO[];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
|
@ -37,7 +39,7 @@ export function useUsers() {
|
||||||
username,
|
username,
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
roleNames: roleName ? [roleName] : [],
|
roleNames: roleName ? [roleName as RoleName] : [],
|
||||||
});
|
});
|
||||||
setState((s) => ({ users: [...s.users, user], loading: false, error: null }));
|
setState((s) => ({ users: [...s.users, user], loading: false, error: null }));
|
||||||
return user;
|
return user;
|
||||||
|
|
@ -79,7 +81,7 @@ export function useUsers() {
|
||||||
|
|
||||||
const assignRole = useCallback(async (id: string, roleName: string) => {
|
const assignRole = useCallback(async (id: string, roleName: string) => {
|
||||||
try {
|
try {
|
||||||
const user = await client.users.assignRole(id, { roleName });
|
const user = await client.users.assignRole(id, { roleName: roleName as RoleName });
|
||||||
setState((s) => ({
|
setState((s) => ({
|
||||||
...s,
|
...s,
|
||||||
users: s.users.map((u) => (u.id === id ? user : u)),
|
users: s.users.map((u) => (u.id === id ? user : u)),
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { AxiosInstance } from 'axios';
|
import type { AxiosInstance } from 'axios';
|
||||||
|
import type { RoleDTO } from '@effigenix/types';
|
||||||
import { API_PATHS } from '@effigenix/config';
|
import { API_PATHS } from '@effigenix/config';
|
||||||
|
|
||||||
export interface RoleDTO {
|
export type { RoleDTO };
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
permissions: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createRolesResource(client: AxiosInstance) {
|
export function createRolesResource(client: AxiosInstance) {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue