feat: Implement batch item removal and pedido splitting for pedidos, and add document management for atas.
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user