From 0a4be24655dfe13c3b373fce2a3b2fabdb6b3f68 Mon Sep 17 00:00:00 2001 From: killer-cf Date: Thu, 18 Dec 2025 16:57:52 -0300 Subject: [PATCH] feat: Add planning cloning functionality to list and detail pages with backend support. --- .../pedidos/planejamento/+page.svelte | 70 +++++-- .../pedidos/planejamento/[id]/+page.svelte | 190 +++++++++++++++++- packages/backend/convex/planejamentos.ts | 29 ++- 3 files changed, 263 insertions(+), 26 deletions(-) diff --git a/apps/web/src/routes/(dashboard)/pedidos/planejamento/+page.svelte b/apps/web/src/routes/(dashboard)/pedidos/planejamento/+page.svelte index 6303291..60af1f3 100644 --- a/apps/web/src/routes/(dashboard)/pedidos/planejamento/+page.svelte +++ b/apps/web/src/routes/(dashboard)/pedidos/planejamento/+page.svelte @@ -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 @@ - - - Abrir - +
+ + + Abrir + + +
{/each} @@ -214,7 +248,9 @@ -

Novo planejamento

+

+ {form.sourcePlanningId ? 'Clonar planejamento' : 'Novo planejamento'} +

@@ -299,7 +335,13 @@ {#if creating} {/if} - {creating ? 'Criando...' : 'Criar'} + {creating + ? form.sourcePlanningId + ? 'Clonando...' + : 'Criando...' + : form.sourcePlanningId + ? 'Clonar' + : 'Criar'}
diff --git a/apps/web/src/routes/(dashboard)/pedidos/planejamento/[id]/+page.svelte b/apps/web/src/routes/(dashboard)/pedidos/planejamento/[id]/+page.svelte index 42f5ff5..8e9e23e 100644 --- a/apps/web/src/routes/(dashboard)/pedidos/planejamento/[id]/+page.svelte +++ b/apps/web/src/routes/(dashboard)/pedidos/planejamento/[id]/+page.svelte @@ -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; + } + } @@ -375,6 +433,10 @@
+ {#if isRascunho} {#if editingHeader}
@@ -803,9 +865,9 @@
- + + {/if} + + {#if showCreate} + {/if} diff --git a/packages/backend/convex/planejamentos.ts b/packages/backend/convex/planejamentos.ts index e00f272..4eb0c1c 100644 --- a/packages/backend/convex/planejamentos.ts +++ b/packages/backend/convex/planejamentos.ts @@ -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; } });