feat: supplier name autocomplete on received invoices
- Added GET /api/admin/received-invoices/suppliers endpoint (distinct names) - Upload and edit forms use HTML datalist for browser-native autocomplete - Suggestions loaded once on page mount Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -135,6 +135,9 @@ export default function ReceivedInvoices({ statsMonth, statsYear, uploadOpen, se
|
|||||||
const [deleting, setDeleting] = useState(false)
|
const [deleting, setDeleting] = useState(false)
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
|
|
||||||
|
// Supplier autocomplete
|
||||||
|
const [supplierNames, setSupplierNames] = useState<string[]>([])
|
||||||
|
|
||||||
// Upload state
|
// Upload state
|
||||||
const [uploadFiles, setUploadFiles] = useState<File[]>([])
|
const [uploadFiles, setUploadFiles] = useState<File[]>([])
|
||||||
const [uploadMeta, setUploadMeta] = useState<UploadMeta[]>([])
|
const [uploadMeta, setUploadMeta] = useState<UploadMeta[]>([])
|
||||||
@@ -177,6 +180,14 @@ export default function ReceivedInvoices({ statsMonth, statsYear, uploadOpen, se
|
|||||||
|
|
||||||
useEffect(() => { fetchList() }, [fetchList])
|
useEffect(() => { fetchList() }, [fetchList])
|
||||||
|
|
||||||
|
// Fetch supplier names for autocomplete
|
||||||
|
useEffect(() => {
|
||||||
|
apiFetch(`${API_BASE}/received-invoices/suppliers`)
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(d => { if (d.success) setSupplierNames(d.data || []) })
|
||||||
|
.catch(() => {})
|
||||||
|
}, [])
|
||||||
|
|
||||||
// Fetch stats (silent refresh without animation)
|
// Fetch stats (silent refresh without animation)
|
||||||
const refreshStats = useCallback(async () => {
|
const refreshStats = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -737,9 +748,11 @@ export default function ReceivedInvoices({ statsMonth, statsYear, uploadOpen, se
|
|||||||
<FormField label="Dodavatel" error={uploadErrors[idx]?.supplier_name} required>
|
<FormField label="Dodavatel" error={uploadErrors[idx]?.supplier_name} required>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
list="supplier-suggestions"
|
||||||
className={`admin-form-input${uploadErrors[idx]?.supplier_name ? ' has-error' : ''}`}
|
className={`admin-form-input${uploadErrors[idx]?.supplier_name ? ' has-error' : ''}`}
|
||||||
value={uploadMeta[idx]?.supplier_name || ''}
|
value={uploadMeta[idx]?.supplier_name || ''}
|
||||||
onChange={(e) => updateMeta(idx, 'supplier_name', e.target.value)}
|
onChange={(e) => updateMeta(idx, 'supplier_name', e.target.value)}
|
||||||
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField label="Č. faktury">
|
<FormField label="Č. faktury">
|
||||||
@@ -860,10 +873,12 @@ export default function ReceivedInvoices({ statsMonth, statsYear, uploadOpen, se
|
|||||||
<FormField label="Dodavatel" required>
|
<FormField label="Dodavatel" required>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
list="supplier-suggestions"
|
||||||
className="admin-form-input"
|
className="admin-form-input"
|
||||||
value={editInvoice.supplier_name}
|
value={editInvoice.supplier_name}
|
||||||
onChange={(e) => setEditInvoice(prev => prev ? { ...prev, supplier_name: e.target.value } : null)}
|
onChange={(e) => setEditInvoice(prev => prev ? { ...prev, supplier_name: e.target.value } : null)}
|
||||||
readOnly={ro}
|
readOnly={ro}
|
||||||
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField label="Č. faktury">
|
<FormField label="Č. faktury">
|
||||||
@@ -991,6 +1006,12 @@ export default function ReceivedInvoices({ statsMonth, statsYear, uploadOpen, se
|
|||||||
type="danger"
|
type="danger"
|
||||||
loading={deleting}
|
loading={deleting}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<datalist id="supplier-suggestions">
|
||||||
|
{supplierNames.map(name => (
|
||||||
|
<option key={name} value={name} />
|
||||||
|
))}
|
||||||
|
</datalist>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,16 @@ export default async function receivedInvoicesRoutes(fastify: FastifyInstance):
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /api/admin/received-invoices/suppliers — distinct supplier names for autocomplete
|
||||||
|
fastify.get('/suppliers', { preHandler: requirePermission('invoices.view') }, async (_request, reply) => {
|
||||||
|
const results = await prisma.received_invoices.findMany({
|
||||||
|
select: { supplier_name: true },
|
||||||
|
distinct: ['supplier_name'],
|
||||||
|
orderBy: { supplier_name: 'asc' },
|
||||||
|
});
|
||||||
|
return success(reply, results.map(r => r.supplier_name));
|
||||||
|
});
|
||||||
|
|
||||||
// GET /api/admin/received-invoices/:id/file
|
// GET /api/admin/received-invoices/:id/file
|
||||||
fastify.get<{ Params: { id: string } }>('/:id/file', { preHandler: requirePermission('invoices.view') }, async (request, reply) => {
|
fastify.get<{ Params: { id: string } }>('/:id/file', { preHandler: requirePermission('invoices.view') }, async (request, reply) => {
|
||||||
const id = parseId(request.params.id, reply);
|
const id = parseId(request.params.id, reply);
|
||||||
|
|||||||
Reference in New Issue
Block a user