Commit 24bf9acc1330cecdcb366b7b8fa15202458addc5

Authored by zichun
1 parent 7a3ebe64

fix(web): DynamicExtFields on edit pages + user-friendly error messages

web/src/api/client.ts
... ... @@ -110,8 +110,12 @@ export async function apiFetch<T>(
110 110 if (text) {
111 111 try {
112 112 body = JSON.parse(text)
113   - const m = (body as { message?: unknown }).message
114   - if (typeof m === 'string') message = m
  113 + // Spring ProblemDetail uses "detail"; fallback to "message"
  114 + const parsed = body as { detail?: unknown; message?: unknown }
  115 + const d = parsed.detail
  116 + const m = parsed.message
  117 + if (typeof d === 'string') message = d
  118 + else if (typeof m === 'string') message = m
115 119 } catch {
116 120 body = text
117 121 message = text
... ... @@ -171,7 +175,8 @@ export const catalog = {
171 175 itemType: string; baseUomCode: string; active?: boolean
172 176 }) => apiFetch<Item>('/api/v1/catalog/items', { method: 'POST', body: JSON.stringify(body) }),
173 177 updateItem: (id: string, body: {
174   - name?: string; description?: string | null; itemType?: string; active?: boolean
  178 + name?: string; description?: string | null; itemType?: string; active?: boolean;
  179 + ext?: Record<string, unknown>
175 180 }) => apiFetch<Item>(`/api/v1/catalog/items/${id}`, { method: 'PATCH', body: JSON.stringify(body) }),
176 181 listUoms: () => apiFetch<Uom[]>('/api/v1/catalog/uoms'),
177 182 }
... ... @@ -188,7 +193,8 @@ export const partners = {
188 193 }) => apiFetch<Partner>('/api/v1/partners/partners', { method: 'POST', body: JSON.stringify(body) }),
189 194 update: (id: string, body: {
190 195 name?: string; type?: string;
191   - email?: string | null; phone?: string | null
  196 + email?: string | null; phone?: string | null;
  197 + ext?: Record<string, unknown>
192 198 }) => apiFetch<Partner>(`/api/v1/partners/partners/${id}`, { method: 'PATCH', body: JSON.stringify(body) }),
193 199 }
194 200  
... ...
web/src/pages/EditItemPage.tsx
... ... @@ -5,6 +5,7 @@ import type { Item } from &#39;@/types/api&#39;
5 5 import { PageHeader } from '@/components/PageHeader'
6 6 import { Loading } from '@/components/Loading'
7 7 import { ErrorBox } from '@/components/ErrorBox'
  8 +import { DynamicExtFields } from '@/components/DynamicExtFields'
8 9  
9 10 const ITEM_TYPES = ['GOOD', 'SERVICE', 'DIGITAL'] as const
10 11  
... ... @@ -17,6 +18,7 @@ export function EditItemPage() {
17 18 const [itemType, setItemType] = useState<string>('GOOD')
18 19 const [active, setActive] = useState(true)
19 20 const [loading, setLoading] = useState(true)
  21 + const [ext, setExt] = useState<Record<string, unknown>>({})
20 22 const [submitting, setSubmitting] = useState(false)
21 23 const [error, setError] = useState<Error | null>(null)
22 24  
... ... @@ -28,6 +30,7 @@ export function EditItemPage() {
28 30 setDescription(i.description ?? '')
29 31 setItemType(i.itemType)
30 32 setActive(i.active)
  33 + setExt(i.ext || {})
31 34 })
32 35 .catch((e: unknown) => setError(e instanceof Error ? e : new Error(String(e))))
33 36 .finally(() => setLoading(false))
... ... @@ -41,6 +44,7 @@ export function EditItemPage() {
41 44 await catalog.updateItem(id, {
42 45 name, itemType, active,
43 46 description: description || null,
  47 + ...(Object.keys(ext).length > 0 ? { ext } : {}),
44 48 })
45 49 navigate('/items')
46 50 } catch (err: unknown) {
... ... @@ -85,6 +89,7 @@ export function EditItemPage() {
85 89 className="rounded border-slate-300" id="active" />
86 90 <label htmlFor="active" className="text-sm text-slate-700">Active</label>
87 91 </div>
  92 + <DynamicExtFields entityName="Item" values={ext} onChange={(k, v) => setExt(prev => ({ ...prev, [k]: v }))} />
88 93 {error && <ErrorBox error={error} />}
89 94 <button type="submit" className="btn-primary" disabled={submitting}>
90 95 {submitting ? 'Saving…' : 'Save Changes'}
... ...
web/src/pages/EditPartnerPage.tsx
... ... @@ -5,6 +5,7 @@ import type { Partner } from &#39;@/types/api&#39;
5 5 import { PageHeader } from '@/components/PageHeader'
6 6 import { Loading } from '@/components/Loading'
7 7 import { ErrorBox } from '@/components/ErrorBox'
  8 +import { DynamicExtFields } from '@/components/DynamicExtFields'
8 9  
9 10 const PARTNER_TYPES = ['CUSTOMER', 'SUPPLIER', 'BOTH'] as const
10 11  
... ... @@ -17,6 +18,7 @@ export function EditPartnerPage() {
17 18 const [email, setEmail] = useState('')
18 19 const [phone, setPhone] = useState('')
19 20 const [loading, setLoading] = useState(true)
  21 + const [ext, setExt] = useState<Record<string, unknown>>({})
20 22 const [submitting, setSubmitting] = useState(false)
21 23 const [error, setError] = useState<Error | null>(null)
22 24  
... ... @@ -28,6 +30,7 @@ export function EditPartnerPage() {
28 30 setType(p.type)
29 31 setEmail(p.email ?? '')
30 32 setPhone(p.phone ?? '')
  33 + setExt(p.ext || {})
31 34 })
32 35 .catch((e: unknown) => setError(e instanceof Error ? e : new Error(String(e))))
33 36 .finally(() => setLoading(false))
... ... @@ -42,6 +45,7 @@ export function EditPartnerPage() {
42 45 name, type,
43 46 email: email || null,
44 47 phone: phone || null,
  48 + ...(Object.keys(ext).length > 0 ? { ext } : {}),
45 49 })
46 50 navigate('/partners')
47 51 } catch (err: unknown) {
... ... @@ -86,6 +90,7 @@ export function EditPartnerPage() {
86 90 className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
87 91 </div>
88 92 </div>
  93 + <DynamicExtFields entityName="Partner" values={ext} onChange={(k, v) => setExt(prev => ({ ...prev, [k]: v }))} />
89 94 {error && <ErrorBox error={error} />}
90 95 <button type="submit" className="btn-primary" disabled={submitting}>
91 96 {submitting ? 'Saving…' : 'Save Changes'}
... ...