feat: Enhance pedidos management with detailed item linking, object search, and improved UI for item configuration and details
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import { api } from '@sgse-app/backend/convex/_generated/api';
|
||||
import type { Doc, Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
||||
import { useConvexClient, useQuery } from 'convex-svelte';
|
||||
import { Pencil, Plus, Trash2, X } from 'lucide-svelte';
|
||||
import { Pencil, Plus, Trash2, X, Search, Check } from 'lucide-svelte';
|
||||
|
||||
const client = useConvexClient();
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
const empresasQuery = useQuery(api.empresas.list, {});
|
||||
let empresas = $derived(empresasQuery.data || []);
|
||||
|
||||
const objetosQuery = useQuery(api.objetos.list, {});
|
||||
let objetos = $derived(objetosQuery.data || []);
|
||||
|
||||
// Modal state
|
||||
let showModal = $state(false);
|
||||
let editingId: string | null = $state(null);
|
||||
@@ -25,9 +28,16 @@
|
||||
dataInicio: '',
|
||||
dataFim: ''
|
||||
});
|
||||
let selectedObjetos = $state<Id<'objetos'>[]>([]);
|
||||
let searchObjeto = $state('');
|
||||
let saving = $state(false);
|
||||
|
||||
function openModal(ata?: Doc<'atas'>) {
|
||||
// Derived state for filtered objects
|
||||
let filteredObjetos = $derived(
|
||||
objetos.filter((obj) => obj.nome.toLowerCase().includes(searchObjeto.toLowerCase()))
|
||||
);
|
||||
|
||||
async function openModal(ata?: Doc<'atas'>) {
|
||||
if (ata) {
|
||||
editingId = ata._id;
|
||||
formData = {
|
||||
@@ -37,6 +47,9 @@
|
||||
dataInicio: ata.dataInicio || '',
|
||||
dataFim: ata.dataFim || ''
|
||||
};
|
||||
// Fetch linked objects
|
||||
const linkedObjetos = await client.query(api.atas.getObjetos, { id: ata._id });
|
||||
selectedObjetos = linkedObjetos.map((o) => o._id);
|
||||
} else {
|
||||
editingId = null;
|
||||
formData = {
|
||||
@@ -46,7 +59,9 @@
|
||||
dataInicio: '',
|
||||
dataFim: ''
|
||||
};
|
||||
selectedObjetos = [];
|
||||
}
|
||||
searchObjeto = '';
|
||||
showModal = true;
|
||||
}
|
||||
|
||||
@@ -55,6 +70,14 @@
|
||||
editingId = null;
|
||||
}
|
||||
|
||||
function toggleObjeto(id: Id<'objetos'>) {
|
||||
if (selectedObjetos.includes(id)) {
|
||||
selectedObjetos = selectedObjetos.filter((oid) => oid !== id);
|
||||
} else {
|
||||
selectedObjetos = [...selectedObjetos, id];
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
if (!formData.empresaId) {
|
||||
@@ -68,7 +91,8 @@
|
||||
numeroSei: formData.numeroSei,
|
||||
empresaId: formData.empresaId as Id<'empresas'>,
|
||||
dataInicio: formData.dataInicio || undefined,
|
||||
dataFim: formData.dataFim || undefined
|
||||
dataFim: formData.dataFim || undefined,
|
||||
objetosIds: selectedObjetos
|
||||
};
|
||||
|
||||
if (editingId) {
|
||||
@@ -185,7 +209,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-md rounded-lg bg-white p-8 shadow-xl">
|
||||
<div class="relative 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"
|
||||
@@ -195,75 +219,128 @@
|
||||
<h2 class="mb-6 text-xl font-bold">{editingId ? 'Editar' : 'Nova'} Ata</h2>
|
||||
|
||||
<form onsubmit={handleSubmit}>
|
||||
<div class="mb-4">
|
||||
<label class="mb-2 block text-sm font-bold text-gray-700" for="numero">
|
||||
Número da Ata
|
||||
</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="numero"
|
||||
type="text"
|
||||
bind:value={formData.numero}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="mb-2 block text-sm font-bold text-gray-700" for="numeroSei">
|
||||
Número SEI
|
||||
</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="numeroSei"
|
||||
type="text"
|
||||
bind:value={formData.numeroSei}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="mb-2 block text-sm font-bold text-gray-700" for="empresa">
|
||||
Empresa
|
||||
</label>
|
||||
<select
|
||||
class="focus:shadow-outline w-full rounded border px-3 py-2 leading-tight text-gray-700 shadow focus:outline-none"
|
||||
id="empresa"
|
||||
bind:value={formData.empresaId}
|
||||
required
|
||||
>
|
||||
<option value="">Selecione uma empresa...</option>
|
||||
{#each empresas as empresa (empresa._id)}
|
||||
<option value={empresa._id}>{empresa.razao_social}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-6 grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-bold text-gray-700" for="dataInicio">
|
||||
Data Início
|
||||
</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="dataInicio"
|
||||
type="date"
|
||||
bind:value={formData.dataInicio}
|
||||
/>
|
||||
<div class="mb-4">
|
||||
<label class="mb-2 block text-sm font-bold text-gray-700" for="numero">
|
||||
Número da Ata
|
||||
</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="numero"
|
||||
type="text"
|
||||
bind:value={formData.numero}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="mb-2 block text-sm font-bold text-gray-700" for="numeroSei">
|
||||
Número SEI
|
||||
</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="numeroSei"
|
||||
type="text"
|
||||
bind:value={formData.numeroSei}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="mb-2 block text-sm font-bold text-gray-700" for="empresa">
|
||||
Empresa
|
||||
</label>
|
||||
<select
|
||||
class="focus:shadow-outline w-full rounded border px-3 py-2 leading-tight text-gray-700 shadow focus:outline-none"
|
||||
id="empresa"
|
||||
bind:value={formData.empresaId}
|
||||
required
|
||||
>
|
||||
<option value="">Selecione uma empresa...</option>
|
||||
{#each empresas as empresa (empresa._id)}
|
||||
<option value={empresa._id}>{empresa.razao_social}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-4 grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-bold text-gray-700" for="dataInicio">
|
||||
Data Início
|
||||
</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="dataInicio"
|
||||
type="date"
|
||||
bind:value={formData.dataInicio}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-bold text-gray-700" for="dataFim">
|
||||
Data Fim
|
||||
</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="dataFim"
|
||||
type="date"
|
||||
bind:value={formData.dataFim}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-bold text-gray-700" for="dataFim">
|
||||
Data Fim
|
||||
|
||||
<div class="flex flex-col">
|
||||
<label class="mb-2 block text-sm font-bold text-gray-700" for="objetos">
|
||||
Objetos Vinculados ({selectedObjetos.length})
|
||||
</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="dataFim"
|
||||
type="date"
|
||||
bind:value={formData.dataFim}
|
||||
/>
|
||||
|
||||
<div class="relative mb-2">
|
||||
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<Search size={16} class="text-gray-400" />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Buscar objetos..."
|
||||
class="focus:shadow-outline w-full rounded border py-2 pr-3 pl-10 text-sm leading-tight text-gray-700 shadow focus:outline-none"
|
||||
bind:value={searchObjeto}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex-1 overflow-y-auto rounded border bg-gray-50 p-2"
|
||||
style="max-height: 300px;"
|
||||
>
|
||||
{#if filteredObjetos.length === 0}
|
||||
<p class="py-4 text-center text-sm text-gray-500">Nenhum objeto encontrado.</p>
|
||||
{:else}
|
||||
<div class="space-y-1">
|
||||
{#each filteredObjetos as objeto (objeto._id)}
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center justify-between rounded px-3 py-2 text-left text-sm hover:bg-gray-200 {selectedObjetos.includes(
|
||||
objeto._id
|
||||
)
|
||||
? 'bg-blue-50 text-blue-700'
|
||||
: ''}"
|
||||
onclick={() => toggleObjeto(objeto._id)}
|
||||
>
|
||||
<span class="truncate">{objeto.nome}</span>
|
||||
{#if selectedObjetos.includes(objeto._id)}
|
||||
<Check size={16} class="text-blue-600" />
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-gray-500">
|
||||
Selecione os objetos que fazem parte desta Ata.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end">
|
||||
<div class="mt-6 flex items-center justify-end border-t pt-4">
|
||||
<button
|
||||
type="button"
|
||||
onclick={closeModal}
|
||||
|
||||
Reference in New Issue
Block a user