Feat pedidos #70
@@ -6,7 +6,7 @@
|
||||
import PageShell from '$lib/components/layout/PageShell.svelte';
|
||||
import TableCard from '$lib/components/ui/TableCard.svelte';
|
||||
import { useConvexClient, useQuery } from 'convex-svelte';
|
||||
import { ClipboardList, Eye, Plus, X } from 'lucide-svelte';
|
||||
import { ClipboardList, Eye, Plus, X, Copy } from 'lucide-svelte';
|
||||
import { resolve } from '$app/paths';
|
||||
import { goto } from '$app/navigation';
|
||||
import { toast } from 'svelte-sonner';
|
||||
@@ -60,11 +60,31 @@
|
||||
descricao: '',
|
||||
data: '',
|
||||
responsavelId: '' as string,
|
||||
acaoId: '' as string
|
||||
acaoId: '' as string,
|
||||
sourcePlanningId: '' as string
|
||||
});
|
||||
|
||||
function openCreate() {
|
||||
form = { titulo: '', descricao: '', data: '', responsavelId: '', acaoId: '' };
|
||||
form = {
|
||||
titulo: '',
|
||||
descricao: '',
|
||||
data: '',
|
||||
responsavelId: '',
|
||||
acaoId: '',
|
||||
sourcePlanningId: ''
|
||||
};
|
||||
showCreate = true;
|
||||
}
|
||||
|
||||
function openClone(planning: (typeof planejamentos)[0]) {
|
||||
form = {
|
||||
titulo: `${planning.titulo} (Cópia)`,
|
||||
descricao: planning.descricao,
|
||||
data: planning.data,
|
||||
responsavelId: planning.responsavelId,
|
||||
acaoId: planning.acaoId || '',
|
||||
sourcePlanningId: planning._id
|
||||
};
|
||||
showCreate = true;
|
||||
}
|
||||
|
||||
@@ -85,7 +105,11 @@
|
||||
descricao: form.descricao,
|
||||
data: form.data,
|
||||
responsavelId: form.responsavelId as Id<'funcionarios'>,
|
||||
acaoId: form.acaoId ? (form.acaoId as Id<'acoes'>) : undefined
|
||||
|
||||
acaoId: form.acaoId ? (form.acaoId as Id<'acoes'>) : undefined,
|
||||
sourcePlanningId: form.sourcePlanningId
|
||||
? (form.sourcePlanningId as Id<'planejamentosPedidos'>)
|
||||
: undefined
|
||||
});
|
||||
toast.success('Planejamento criado.');
|
||||
showCreate = false;
|
||||
@@ -184,14 +208,24 @@
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-right whitespace-nowrap">
|
||||
<a
|
||||
href={resolve(`/pedidos/planejamento/${p._id}`)}
|
||||
class="btn btn-ghost btn-sm gap-2"
|
||||
aria-label="Abrir planejamento"
|
||||
>
|
||||
<Eye class="h-4 w-4" />
|
||||
Abrir
|
||||
</a>
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<a
|
||||
href={resolve(`/pedidos/planejamento/${p._id}`)}
|
||||
class="btn btn-ghost btn-sm gap-2"
|
||||
aria-label="Abrir planejamento"
|
||||
>
|
||||
<Eye class="h-4 w-4" />
|
||||
Abrir
|
||||
</a>
|
||||
<button
|
||||
class="btn btn-ghost btn-sm gap-2"
|
||||
onclick={() => openClone(p)}
|
||||
aria-label="Clonar planejamento"
|
||||
>
|
||||
<Copy class="h-4 w-4" />
|
||||
Clonar
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
@@ -214,7 +248,9 @@
|
||||
<X class="h-5 w-5" />
|
||||
</button>
|
||||
|
||||
<h3 class="text-lg font-bold">Novo planejamento</h3>
|
||||
<h3 class="text-lg font-bold">
|
||||
{form.sourcePlanningId ? 'Clonar planejamento' : 'Novo planejamento'}
|
||||
</h3>
|
||||
|
||||
<div class="mt-6 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="form-control w-full md:col-span-2">
|
||||
@@ -299,7 +335,13 @@
|
||||
{#if creating}
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
{/if}
|
||||
{creating ? 'Criando...' : 'Criar'}
|
||||
{creating
|
||||
? form.sourcePlanningId
|
||||
? 'Clonando...'
|
||||
: 'Criando...'
|
||||
: form.sourcePlanningId
|
||||
? 'Clonar'
|
||||
: 'Criar'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
import { useConvexClient, useQuery } from 'convex-svelte';
|
||||
import { page } from '$app/state';
|
||||
import { resolve } from '$app/paths';
|
||||
import { goto } from '$app/navigation';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { Plus, Trash2, X, Save, Edit } from 'lucide-svelte';
|
||||
import { Plus, Trash2, X, Save, Edit, Copy } from 'lucide-svelte';
|
||||
|
||||
const client = useConvexClient();
|
||||
const planejamentoId = $derived(page.params.id as Id<'planejamentosPedidos'>);
|
||||
@@ -323,6 +324,63 @@
|
||||
gerando = false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Clone/Create Modal logic (duplicated from list page for now) ---
|
||||
let showCreate = $state(false);
|
||||
let creating = $state(false);
|
||||
let createForm = $state({
|
||||
titulo: '',
|
||||
descricao: '',
|
||||
data: '',
|
||||
responsavelId: '' as string,
|
||||
acaoId: '' as string,
|
||||
sourcePlanningId: '' as string
|
||||
});
|
||||
|
||||
function openClone() {
|
||||
if (!planejamento) return;
|
||||
createForm = {
|
||||
titulo: `${planejamento.titulo} (Cópia)`,
|
||||
descricao: planejamento.descricao,
|
||||
data: planejamento.data,
|
||||
responsavelId: planejamento.responsavelId,
|
||||
acaoId: planejamento.acaoId || '',
|
||||
sourcePlanningId: planejamento._id
|
||||
};
|
||||
showCreate = true;
|
||||
}
|
||||
|
||||
function closeCreate() {
|
||||
showCreate = false;
|
||||
}
|
||||
|
||||
async function handleCreate() {
|
||||
if (!createForm.titulo.trim()) return toast.error('Informe um título.');
|
||||
if (!createForm.descricao.trim()) return toast.error('Informe uma descrição.');
|
||||
if (!createForm.data.trim()) return toast.error('Informe uma data.');
|
||||
if (!createForm.responsavelId) return toast.error('Selecione um responsável.');
|
||||
|
||||
creating = true;
|
||||
try {
|
||||
const id = await client.mutation(api.planejamentos.create, {
|
||||
titulo: createForm.titulo,
|
||||
descricao: createForm.descricao,
|
||||
data: createForm.data,
|
||||
responsavelId: createForm.responsavelId as Id<'funcionarios'>,
|
||||
acaoId: createForm.acaoId ? (createForm.acaoId as Id<'acoes'>) : undefined,
|
||||
sourcePlanningId: createForm.sourcePlanningId
|
||||
? (createForm.sourcePlanningId as Id<'planejamentosPedidos'>)
|
||||
: undefined
|
||||
});
|
||||
toast.success('Planejamento clonado com sucesso.');
|
||||
showCreate = false;
|
||||
await goto(resolve(`/pedidos/planejamento/${id}`));
|
||||
} catch (e) {
|
||||
toast.error((e as Error).message);
|
||||
} finally {
|
||||
creating = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<PageShell>
|
||||
@@ -375,6 +433,10 @@
|
||||
</div>
|
||||
|
||||
<div class="flex shrink-0 flex-col items-end gap-2">
|
||||
<button class="btn btn-ghost btn-sm gap-2" onclick={openClone}>
|
||||
<Copy class="h-4 w-4" />
|
||||
Clonar
|
||||
</button>
|
||||
{#if isRascunho}
|
||||
{#if editingHeader}
|
||||
<div class="flex gap-2">
|
||||
@@ -803,9 +865,9 @@
|
||||
</div>
|
||||
|
||||
<div class="modal-action">
|
||||
<button type="button" class="btn" onclick={closeAddItemModal} disabled={addingItem}>
|
||||
Cancelar
|
||||
</button>
|
||||
<button type="button" class="btn" onclick={closeAddItemModal} disabled={addingItem}
|
||||
>Cancelar</button
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
@@ -815,15 +877,123 @@
|
||||
{#if addingItem}
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
{/if}
|
||||
{addingItem ? 'Adicionando...' : 'Adicionar'}
|
||||
Adicionar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="modal-backdrop"
|
||||
onclick={closeAddItemModal}
|
||||
aria-label="Fechar modal"
|
||||
<button type="button" class="modal-backdrop" onclick={closeAddItemModal} aria-label="Fechar"
|
||||
></button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if showCreate}
|
||||
<div class="modal modal-open">
|
||||
<div class="modal-box max-w-2xl">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-circle absolute top-2 right-2"
|
||||
onclick={closeCreate}
|
||||
aria-label="Fechar modal"
|
||||
disabled={creating}
|
||||
>
|
||||
<X class="h-5 w-5" />
|
||||
</button>
|
||||
|
||||
<h3 class="text-lg font-bold">Clonar planejamento</h3>
|
||||
|
||||
<div class="mt-6 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="form-control w-full md:col-span-2">
|
||||
<label class="label" for="c_titulo">
|
||||
<span class="label-text font-semibold">Título</span>
|
||||
</label>
|
||||
<input
|
||||
id="c_titulo"
|
||||
type="text"
|
||||
class="input input-bordered focus:input-primary w-full"
|
||||
bind:value={createForm.titulo}
|
||||
disabled={creating}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full md:col-span-2">
|
||||
<label class="label" for="c_descricao">
|
||||
<span class="label-text font-semibold">Descrição</span>
|
||||
</label>
|
||||
<textarea
|
||||
id="c_descricao"
|
||||
class="textarea textarea-bordered focus:textarea-primary w-full"
|
||||
rows="4"
|
||||
bind:value={createForm.descricao}
|
||||
disabled={creating}
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
<label class="label" for="c_data">
|
||||
<span class="label-text font-semibold">Data</span>
|
||||
</label>
|
||||
<input
|
||||
id="c_data"
|
||||
type="date"
|
||||
class="input input-bordered focus:input-primary w-full"
|
||||
bind:value={createForm.data}
|
||||
disabled={creating}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
<label class="label" for="c_responsavel">
|
||||
<span class="label-text font-semibold">Responsável</span>
|
||||
</label>
|
||||
<select
|
||||
id="c_responsavel"
|
||||
class="select select-bordered focus:select-primary w-full"
|
||||
bind:value={createForm.responsavelId}
|
||||
disabled={creating || funcionariosQuery.isLoading}
|
||||
>
|
||||
<option value="">Selecione...</option>
|
||||
{#each funcionariosQuery.data || [] as f (f._id)}
|
||||
<option value={f._id}>{f.nome}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full md:col-span-2">
|
||||
<label class="label" for="c_acao">
|
||||
<span class="label-text font-semibold">Ação (opcional)</span>
|
||||
</label>
|
||||
<select
|
||||
id="c_acao"
|
||||
class="select select-bordered focus:select-primary w-full"
|
||||
bind:value={createForm.acaoId}
|
||||
disabled={creating || acoesQuery.isLoading}
|
||||
>
|
||||
<option value="">Nenhuma</option>
|
||||
{#each acoesQuery.data || [] as a (a._id)}
|
||||
<option value={a._id}>{a.nome}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-action">
|
||||
<button type="button" class="btn" onclick={closeCreate} disabled={creating}
|
||||
>Cancelar</button
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
onclick={handleCreate}
|
||||
disabled={creating}
|
||||
>
|
||||
{#if creating}
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
{/if}
|
||||
Clonar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="modal-backdrop" onclick={closeCreate} aria-label="Fechar"
|
||||
></button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -177,7 +177,8 @@ export const create = mutation({
|
||||
descricao: v.string(),
|
||||
data: v.string(),
|
||||
responsavelId: v.id('funcionarios'),
|
||||
acaoId: v.optional(v.id('acoes'))
|
||||
acaoId: v.optional(v.id('acoes')),
|
||||
sourcePlanningId: v.optional(v.id('planejamentosPedidos'))
|
||||
},
|
||||
returns: v.id('planejamentosPedidos'),
|
||||
handler: async (ctx, args) => {
|
||||
@@ -192,7 +193,7 @@ export const create = mutation({
|
||||
if (!descricao) throw new Error('Informe uma descrição.');
|
||||
if (!data) throw new Error('Informe uma data.');
|
||||
|
||||
return await ctx.db.insert('planejamentosPedidos', {
|
||||
const newItemId = await ctx.db.insert('planejamentosPedidos', {
|
||||
titulo,
|
||||
descricao,
|
||||
data,
|
||||
@@ -203,6 +204,30 @@ export const create = mutation({
|
||||
criadoEm: now,
|
||||
atualizadoEm: now
|
||||
});
|
||||
|
||||
const sourcePlanningId = args.sourcePlanningId;
|
||||
|
||||
if (sourcePlanningId) {
|
||||
const sourceItems = await ctx.db
|
||||
.query('planejamentoItens')
|
||||
.withIndex('by_planejamentoId', (q) => q.eq('planejamentoId', sourcePlanningId))
|
||||
.collect();
|
||||
|
||||
for (const item of sourceItems) {
|
||||
await ctx.db.insert('planejamentoItens', {
|
||||
planejamentoId: newItemId,
|
||||
objetoId: item.objetoId,
|
||||
quantidade: item.quantidade,
|
||||
valorEstimado: item.valorEstimado,
|
||||
numeroDfd: item.numeroDfd,
|
||||
// Não copiamos o pedidoId pois é um novo planejamento
|
||||
criadoEm: now,
|
||||
atualizadoEm: now
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return newItemId;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user