feat: Implement batch item removal and pedido splitting for pedidos, and add document management for atas.

This commit is contained in:
2025-12-03 23:37:26 -03:00
parent fb78866a0e
commit 7746dce25a
6 changed files with 796 additions and 100 deletions

View File

@@ -37,6 +37,14 @@
objetos.filter((obj) => obj.nome.toLowerCase().includes(searchObjeto.toLowerCase()))
);
// Document state
let mainPdfFile: File | null = $state(null);
let attachmentFiles: File[] = $state([]);
let attachments = $state<Array<{ _id: Id<'atasDocumentos'>; nome: string; url: string | null }>>(
[]
);
let uploading = $state(false);
async function openModal(ata?: Doc<'atas'>) {
if (ata) {
editingId = ata._id;
@@ -50,6 +58,9 @@
// Fetch linked objects
const linkedObjetos = await client.query(api.atas.getObjetos, { id: ata._id });
selectedObjetos = linkedObjetos.map((o) => o._id);
// Fetch attachments
attachments = await client.query(api.atas.getDocumentos, { ataId: ata._id });
} else {
editingId = null;
formData = {
@@ -60,7 +71,10 @@
dataFim: ''
};
selectedObjetos = [];
attachments = [];
}
mainPdfFile = null;
attachmentFiles = [];
searchObjeto = '';
showModal = true;
}
@@ -78,6 +92,17 @@
}
}
async function uploadFile(file: File) {
const uploadUrl = await client.mutation(api.atas.generateUploadUrl, {});
const result = await fetch(uploadUrl, {
method: 'POST',
headers: { 'Content-Type': file.type },
body: file
});
const { storageId } = await result.json();
return storageId;
}
async function handleSubmit(e: Event) {
e.preventDefault();
if (!formData.empresaId) {
@@ -86,28 +111,53 @@
}
saving = true;
try {
let pdfStorageId = undefined;
if (mainPdfFile) {
pdfStorageId = await uploadFile(mainPdfFile);
}
const payload = {
numero: formData.numero,
numeroSei: formData.numeroSei,
empresaId: formData.empresaId as Id<'empresas'>,
dataInicio: formData.dataInicio || undefined,
dataFim: formData.dataFim || undefined,
objetosIds: selectedObjetos
objetosIds: selectedObjetos,
pdf: pdfStorageId
};
let ataId: Id<'atas'>;
if (editingId) {
await client.mutation(api.atas.update, {
id: editingId as Id<'atas'>,
...payload
});
ataId = editingId as Id<'atas'>;
} else {
await client.mutation(api.atas.create, payload);
ataId = await client.mutation(api.atas.create, payload);
}
// Upload attachments
if (attachmentFiles.length > 0) {
uploading = true;
for (const file of attachmentFiles) {
const storageId = await uploadFile(file);
await client.mutation(api.atas.saveDocumento, {
ataId,
nome: file.name,
storageId,
tipo: file.type,
tamanho: file.size
});
}
}
closeModal();
} catch (e) {
alert('Erro ao salvar: ' + (e as Error).message);
} finally {
saving = false;
uploading = false;
}
}
@@ -120,9 +170,38 @@
}
}
async function handleDeleteAttachment(docId: Id<'atasDocumentos'>) {
if (!confirm('Tem certeza que deseja excluir este anexo?')) return;
try {
await client.mutation(api.atas.removeDocumento, { id: docId });
// Refresh attachments list
if (editingId) {
attachments = await client.query(api.atas.getDocumentos, {
ataId: editingId as Id<'atas'>
});
}
} catch (e) {
alert('Erro ao excluir anexo: ' + (e as Error).message);
}
}
function getEmpresaNome(id: Id<'empresas'>) {
return empresas.find((e) => e._id === id)?.razao_social || 'Empresa não encontrada';
}
function handleFileSelect(e: Event) {
const input = e.target as HTMLInputElement;
if (input.files && input.files.length > 0) {
mainPdfFile = input.files[0];
}
}
function handleAttachmentsSelect(e: Event) {
const input = e.target as HTMLInputElement;
if (input.files && input.files.length > 0) {
attachmentFiles = Array.from(input.files);
}
}
</script>
<div class="container mx-auto p-6">
@@ -173,7 +252,12 @@
<tr>
<td class="px-6 py-4 font-medium whitespace-nowrap">{ata.numero}</td>
<td class="px-6 py-4 whitespace-nowrap">{ata.numeroSei}</td>
<td class="px-6 py-4 whitespace-nowrap">{getEmpresaNome(ata.empresaId)}</td>
<td
class="max-w-md truncate px-6 py-4 whitespace-nowrap"
title={getEmpresaNome(ata.empresaId)}
>
{getEmpresaNome(ata.empresaId)}
</td>
<td class="px-6 py-4 text-sm whitespace-nowrap text-gray-500">
{ata.dataInicio || '-'} a {ata.dataFim || '-'}
</td>
@@ -209,7 +293,7 @@
<div
class="fixed inset-0 z-50 flex h-full w-full items-center justify-center overflow-y-auto bg-black/40"
>
<div class="relative w-full max-w-2xl rounded-lg bg-white p-8 shadow-xl">
<div class="relative my-8 w-full max-w-2xl rounded-lg bg-white p-8 shadow-xl">
<button
onclick={closeModal}
class="absolute top-4 right-4 text-gray-400 hover:text-gray-600"
@@ -288,6 +372,19 @@
/>
</div>
</div>
<div class="mb-4">
<label class="mb-2 block text-sm font-bold text-gray-700" for="pdf">
PDF da Ata {editingId ? '(Deixe em branco para manter o atual)' : ''}
</label>
<input
class="focus:shadow-outline w-full appearance-none rounded border px-3 py-2 leading-tight text-gray-700 shadow focus:outline-none"
id="pdf"
type="file"
accept=".pdf"
onchange={handleFileSelect}
/>
</div>
</div>
<div class="flex flex-col">
@@ -308,8 +405,8 @@
</div>
<div
class="flex-1 overflow-y-auto rounded border bg-gray-50 p-2"
style="max-height: 300px;"
class="mb-4 flex-1 overflow-y-auto rounded border bg-gray-50 p-2"
style="max-height: 200px;"
>
{#if filteredObjetos.length === 0}
<p class="py-4 text-center text-sm text-gray-500">Nenhum objeto encontrado.</p>
@@ -334,9 +431,45 @@
</div>
{/if}
</div>
<p class="mt-1 text-xs text-gray-500">
Selecione os objetos que fazem parte desta Ata.
</p>
<div class="border-t pt-4">
<label class="mb-2 block text-sm font-bold text-gray-700" for="anexos">
Anexos
</label>
<input
class="focus:shadow-outline mb-2 w-full appearance-none rounded border px-3 py-2 leading-tight text-gray-700 shadow focus:outline-none"
id="anexos"
type="file"
multiple
onchange={handleAttachmentsSelect}
/>
{#if attachments.length > 0}
<div class="mt-2 max-h-40 space-y-2 overflow-y-auto">
{#each attachments as doc (doc._id)}
<div
class="flex items-center justify-between rounded bg-gray-100 p-2 text-sm"
>
<a
href={doc.url}
target="_blank"
rel="noopener noreferrer"
class="max-w-[150px] truncate text-blue-600 hover:underline"
>
{doc.nome}
</a>
<button
type="button"
onclick={() => handleDeleteAttachment(doc._id)}
class="text-red-500 hover:text-red-700"
>
<X size={16} />
</button>
</div>
{/each}
</div>
{/if}
</div>
</div>
</div>
@@ -350,10 +483,10 @@
</button>
<button
type="submit"
disabled={saving}
disabled={saving || uploading}
class="focus:shadow-outline rounded bg-blue-600 px-4 py-2 font-bold text-white hover:bg-blue-700 focus:outline-none disabled:opacity-50"
>
{saving ? 'Salvando...' : 'Salvar'}
{saving || uploading ? 'Salvando...' : 'Salvar'}
</button>
</div>
</form>