feat: Add planning cloning functionality to list and detail pages with backend support.

This commit is contained in:
2025-12-18 16:57:52 -03:00
parent 011a867aac
commit 0a4be24655
3 changed files with 263 additions and 26 deletions

View File

@@ -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>

View File

@@ -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}