Import Tiny product compositions

This commit is contained in:
Cauê Faleiros
2026-07-31 11:32:49 -03:00
parent b431802d50
commit 37d4a7732e
9 changed files with 368 additions and 15 deletions

View File

@@ -1,11 +1,12 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { Link, useSearchParams } from 'react-router-dom';
import { ClipboardList, Loader2, Package, RefreshCw, Ruler, Save, Tags, Trash2 } from 'lucide-react';
import { ClipboardList, Loader2, Package, RefreshCw, Ruler, Save, Tags, Trash2, Upload } from 'lucide-react';
import {
deleteCatalogCategory,
deleteCatalogProduct,
deleteConsumptionReference,
fetchCatalogSummary,
importProductCompositions,
saveCatalogCategory,
saveCatalogProduct,
saveConsumptionReference
@@ -93,7 +94,9 @@ const getAverageYield = (reference: ConsumptionReference) => {
};
const getReferenceSourceLabel = (reference: ConsumptionReference) => (
reference.source === 'tiny_op' ? 'Tiny OP' : 'Manual'
reference.source === 'tiny_op'
? 'Tiny OP'
: reference.source === 'tiny_structure' ? 'Tiny Estrutura' : 'Manual'
);
const formatConsumptionPerPiece = (reference: ConsumptionReference) => {
@@ -111,6 +114,7 @@ const Registrations = () => {
const [status, setStatus] = useState<SaveStatus>('idle');
const [feedback, setFeedback] = useState('');
const appliedSkuPrefillRef = useRef('');
const compositionFileInputRef = useRef<HTMLInputElement>(null);
const [categoryForm, setCategoryForm] = useState({ name: '', description: '' });
const [productForm, setProductForm] = useState(defaultProductForm);
@@ -323,6 +327,29 @@ const Registrations = () => {
setSizeYields({});
}, 'Referencia salva.');
const importCompositionFile = async (file: File | null) => {
if (!file) return;
setStatus('saving');
setFeedback('Importando composições...');
try {
const payload = JSON.parse(await file.text()) as unknown;
const result = await importProductCompositions(payload);
await loadCatalog();
setStatus(result.failed ? 'error' : 'saved');
setFeedback(
`${result.imported} composições importadas, ${result.referenceCount} referências criadas/atualizadas` +
`${result.failed ? `, ${result.failed} falhas` : ''}.`
);
} catch (error) {
setStatus('error');
setFeedback(error instanceof Error ? error.message : 'Não foi possível importar o arquivo.');
} finally {
if (compositionFileInputRef.current) compositionFileInputRef.current.value = '';
}
};
const removeCategory = (category: CatalogCategory) => runAction(
() => deleteCatalogCategory(category.id),
`Categoria ${category.name} excluida.`
@@ -363,6 +390,22 @@ const Registrations = () => {
</p>
</div>
<div className="flex flex-wrap gap-2">
<input
ref={compositionFileInputRef}
type="file"
accept="application/json,.json"
className="hidden"
onChange={(event) => void importCompositionFile(event.target.files?.[0] || null)}
/>
<button
type="button"
onClick={() => compositionFileInputRef.current?.click()}
disabled={status === 'saving'}
className="inline-flex h-10 items-center justify-center gap-2 rounded-xl border border-dark-border bg-dark-card px-4 text-sm font-bold text-dark-text transition-colors hover:border-brand-primary disabled:cursor-not-allowed disabled:opacity-60 cursor-pointer"
>
<Upload className="h-4 w-4 text-brand-primary" />
Importar composições
</button>
<Link
to="/planning-issues"
className="inline-flex h-10 items-center justify-center gap-2 rounded-xl border border-dark-border bg-dark-card px-4 text-sm font-bold text-dark-text transition-colors hover:border-brand-primary"
@@ -674,7 +717,7 @@ const Registrations = () => {
{Object.keys(reference.sizeYields || {}).length ? 'Por tamanho' : 'Geral'}
</span>
<span className={`rounded-full border px-2 py-0.5 text-[10px] font-bold ${
reference.source === 'tiny_op'
reference.source === 'tiny_op' || reference.source === 'tiny_structure'
? 'border-sky-400/30 bg-sky-400/10 text-sky-300'
: 'border-dark-border bg-dark-input text-dark-muted'
}`}>