Botones de edicion y eliminacion en el footer de la informacion de los secotres y doble click para ver la informacion
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import type { Sector } from '$lib/api/dashboard/general_catalogs/sectors';
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
permissions?: { canEdit: boolean; canDelete: boolean }
|
||||
): ColumnDef<Sector>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'key',
|
||||
header: 'Clave',
|
||||
cell: ({ row }) => row.original.key || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'authorized',
|
||||
header: 'Estatus',
|
||||
cell: ({ row }) => (row.original.authorized ? 'Autorizado' : 'No Autorizado')
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: '',
|
||||
cell: ({ row }) =>
|
||||
renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit: permissions?.canEdit ?? false,
|
||||
canDelete: permissions?.canDelete ?? false
|
||||
})
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Switch } from '$lib/components/ui/switch';
|
||||
import { sectorsApi, type Sector } from '$lib/api/dashboard/reference_data/sectors';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: Sector | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(!!item);
|
||||
const title = $derived(isEdit ? 'Editar Sector' : 'Nuevo Sector');
|
||||
|
||||
let formData = $state({ key: '', description: '', authorized: false });
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (!open) return;
|
||||
if (item) {
|
||||
formData = { key: item.key || '', description: item.description || '', authorized: item.authorized ?? false };
|
||||
} else {
|
||||
formData = { key: '', description: '', authorized: false };
|
||||
}
|
||||
error = null;
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
error = null;
|
||||
loading = true;
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
error = 'No hay una empresa seleccionada.';
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
const companyId = companyStore.activeCompany.id;
|
||||
|
||||
try {
|
||||
if (!formData.key.trim()) throw new Error('La clave es requerida');
|
||||
if (!formData.description.trim()) throw new Error('La descripción es requerida');
|
||||
|
||||
const payload = {
|
||||
key: formData.key.trim(),
|
||||
description: formData.description.trim(),
|
||||
authorized: formData.authorized
|
||||
};
|
||||
|
||||
const response = isEdit && item
|
||||
? await sectorsApi.update(item.id, payload, companyId)
|
||||
: await sectorsApi.create(payload, companyId);
|
||||
|
||||
if (response.error) throw new Error(response.error);
|
||||
|
||||
open = false;
|
||||
onSuccess?.();
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="max-w-md">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-2">
|
||||
{#if error}
|
||||
<div class="rounded-md border border-destructive bg-destructive/10 p-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="key">Clave <span class="text-destructive">*</span></Label>
|
||||
<Input id="key" bind:value={formData.key} maxlength={8} placeholder="Ej: XIX" />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="description">Descripción <span class="text-destructive">*</span></Label>
|
||||
<Input id="description" bind:value={formData.description} maxlength={150} />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 pt-1">
|
||||
<Switch id="authorized" bind:checked={formData.authorized} />
|
||||
<Label for="authorized">Autorizado para PROSEC</Label>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => (open = false)}>Cancelar</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Guardando...' : 'Guardar'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,75 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { sectorsApi, type Sector } from '$lib/api/dashboard/reference_data/sectors';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from './create-edit-dialog.svelte';
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess,
|
||||
canEdit = false,
|
||||
canDelete = false
|
||||
}: {
|
||||
item: Sector;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm(`¿Estás seguro de eliminar el sector "${item.key}"?`)) return;
|
||||
if (!companyStore.activeCompany) return;
|
||||
|
||||
loading = true;
|
||||
try {
|
||||
const response = await sectorsApi.delete(item.id, companyStore.activeCompany.id);
|
||||
if (!response.error) onSuccess?.();
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if canEdit || canDelete}
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => (dialogOpen = true)}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
{#if canEdit}<DropdownMenu.Separator />{/if}
|
||||
<DropdownMenu.Item
|
||||
class="text-destructive focus:text-destructive"
|
||||
onclick={handleDelete}
|
||||
disabled={loading}
|
||||
>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog bind:open={dialogOpen} {item} {onSuccess} />
|
||||
{/if}
|
||||
Reference in New Issue
Block a user