Merge pull request 'fix/doble-click-parte' (#294) from fix/doble-click-parte into development
Reviewed-on: ADUANASOFT/anexo76#294
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
selectedIds?: number[];
|
||||
onSelectedIdsChange?: (ids: number[]) => void;
|
||||
onRowClick?: (row: TData) => void;
|
||||
onRowDoubleClick?: (row: TData) => void;
|
||||
};
|
||||
|
||||
let {
|
||||
@@ -26,6 +27,7 @@
|
||||
selectedIds = [],
|
||||
onSelectedIdsChange,
|
||||
onRowClick,
|
||||
onRowDoubleClick,
|
||||
}: InfiniteDataTableProps<TData, TValue> = $props();
|
||||
|
||||
const table = createSvelteTable({
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
loadMore: () => void;
|
||||
selectedId?: number | null;
|
||||
onRowClick?: (row: TData) => void;
|
||||
onRowDoubleClick?: (row: TData) => void;
|
||||
};
|
||||
|
||||
let {
|
||||
@@ -21,7 +22,8 @@
|
||||
hasMore,
|
||||
loadMore,
|
||||
selectedId = null,
|
||||
onRowClick
|
||||
onRowClick,
|
||||
onRowDoubleClick
|
||||
}: DataTableProps<TData, TValue> = $props();
|
||||
|
||||
const table = createSvelteTable({
|
||||
@@ -94,6 +96,7 @@
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && 'selected'}
|
||||
onclick={() => onRowClick?.(row.original)}
|
||||
ondblclick={() => onRowDoubleClick?.(row.original)}
|
||||
class="cursor-pointer {row.getIsSelected() ? 'catalog-table-row-selected' : 'catalog-table-row'}"
|
||||
>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
|
||||
@@ -16,15 +16,34 @@ export function createColumns(): ColumnDef<A76Class>[] {
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const isSelected = row.getIsSelected();
|
||||
const checkboxSnippet = createRawSnippet<[{ selected: boolean }]>((getProps) => {
|
||||
const { selected } = getProps();
|
||||
const checkboxSnippet = createRawSnippet<[
|
||||
{ selected: boolean; onchange: (e: Event) => void }
|
||||
]>((getProps) => {
|
||||
const { selected, onchange } = getProps();
|
||||
return {
|
||||
render: () => `<div class="flex items-center justify-center">
|
||||
<input type="checkbox" tabindex="-1" class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer" ${selected ? 'checked' : ''} />
|
||||
</div>`
|
||||
</div>`,
|
||||
setup: (node) => {
|
||||
const input = node.querySelector('input') as HTMLInputElement | null;
|
||||
input?.addEventListener('change', onchange);
|
||||
}
|
||||
};
|
||||
});
|
||||
return renderSnippet(checkboxSnippet, { selected: isSelected });
|
||||
return renderSnippet(checkboxSnippet, {
|
||||
selected: isSelected,
|
||||
onchange: (e: Event) => {
|
||||
e.stopPropagation();
|
||||
const rowId = row.original.id;
|
||||
if (typeof rowId === 'number') {
|
||||
(
|
||||
row.table.options.meta as
|
||||
| { toggleSelectedId?: (id: number, checked?: boolean) => void }
|
||||
| undefined
|
||||
)?.toggleSelectedId?.(rowId, (e.target as HTMLInputElement).checked);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
size: 40,
|
||||
enableSorting: false,
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
data: TData[];
|
||||
loading: boolean;
|
||||
selectedId?: number | null;
|
||||
onRowClick?: (row: TData) => void;
|
||||
selectedIds?: number[];
|
||||
onSelectedIdsChange?: (selectedIds: number[]) => void;
|
||||
onRowDoubleClick?: (row: TData) => void;
|
||||
sorting?: import("@tanstack/table-core").SortingState;
|
||||
onSortingChange?: (sorting: import("@tanstack/table-core").SortingState) => void;
|
||||
};
|
||||
@@ -20,26 +21,50 @@
|
||||
data,
|
||||
columns,
|
||||
loading,
|
||||
selectedId = null,
|
||||
onRowClick,
|
||||
selectedIds = [],
|
||||
onSelectedIdsChange,
|
||||
onRowDoubleClick,
|
||||
sorting = [],
|
||||
onSortingChange
|
||||
}: DataTableProps<TData, TValue> = $props();
|
||||
|
||||
function getRowIdValue(row: TData): number | null {
|
||||
const candidate = (row as { id?: unknown })?.id;
|
||||
return typeof candidate === 'number' ? candidate : null;
|
||||
}
|
||||
|
||||
function toggleSelectedId(rowId: number, forceSelected?: boolean) {
|
||||
const isSelected = selectedIds.includes(rowId);
|
||||
const shouldSelect = forceSelected ?? !isSelected;
|
||||
|
||||
if (shouldSelect && !isSelected) {
|
||||
onSelectedIdsChange?.([...selectedIds, rowId]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shouldSelect && isSelected) {
|
||||
onSelectedIdsChange?.(selectedIds.filter((id) => id !== rowId));
|
||||
}
|
||||
}
|
||||
|
||||
const table = createSvelteTable({
|
||||
get data() {
|
||||
return data;
|
||||
},
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getRowId: (row: any) => row.id?.toString(),
|
||||
state: {
|
||||
get rowSelection() {
|
||||
return selectedId ? { [selectedId]: true } : {};
|
||||
return Object.fromEntries(selectedIds.map((id) => [id.toString(), true]));
|
||||
},
|
||||
get sorting() {
|
||||
return sorting;
|
||||
}
|
||||
},
|
||||
meta: {
|
||||
toggleSelectedId
|
||||
},
|
||||
onSortingChange: (updater) => {
|
||||
if (onSortingChange) {
|
||||
const nextSorting = typeof updater === 'function' ? updater(sorting) : updater;
|
||||
@@ -48,7 +73,7 @@
|
||||
},
|
||||
manualSorting: true,
|
||||
enableRowSelection: true,
|
||||
enableMultiRowSelection: false
|
||||
enableMultiRowSelection: true
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -129,10 +154,12 @@
|
||||
<Table.Row
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
onclick={() => {
|
||||
if (onRowClick) {
|
||||
onRowClick(row.original);
|
||||
const rowId = getRowIdValue(row.original);
|
||||
if (rowId !== null) {
|
||||
toggleSelectedId(rowId);
|
||||
}
|
||||
}}
|
||||
ondblclick={() => onRowDoubleClick?.(row.original)}
|
||||
class="cursor-pointer hover:bg-muted/50 transition-colors {row.getIsSelected() ? 'bg-primary/10' : ''}"
|
||||
>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
|
||||
@@ -140,10 +140,18 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex-1 overflow-y-auto border rounded-md min-h-[300px]"
|
||||
onscroll={handleScroll}
|
||||
>
|
||||
<div class="flex-1 flex flex-col overflow-hidden border rounded-md min-h-[300px]">
|
||||
<!-- Header fuera del contenedor scrollable para evitar desplazamiento en hit-testing -->
|
||||
<table class="w-full text-sm shrink-0">
|
||||
<thead class="bg-muted/50">
|
||||
<tr class="text-left border-b">
|
||||
<th class="p-3 font-medium text-muted-foreground w-[100px]">Clave</th>
|
||||
<th class="p-3 font-medium text-muted-foreground">Descripción</th>
|
||||
<th class="p-3 font-medium text-muted-foreground w-[80px]">UM</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
<div class="flex-1 overflow-y-auto" onscroll={handleScroll}>
|
||||
{#if loading}
|
||||
<div class="flex flex-col items-center justify-center h-48 gap-2 text-muted-foreground">
|
||||
<Loader2 class="h-8 w-8 animate-spin text-primary" />
|
||||
@@ -155,13 +163,6 @@
|
||||
</div>
|
||||
{:else}
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-muted/50 sticky top-0 backdrop-blur-sm">
|
||||
<tr class="text-left border-b">
|
||||
<th class="p-3 font-medium text-muted-foreground w-[100px]">Clave</th>
|
||||
<th class="p-3 font-medium text-muted-foreground">Descripción</th>
|
||||
<th class="p-3 font-medium text-muted-foreground w-[80px]">UM</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each items as item}
|
||||
<tr
|
||||
@@ -204,6 +205,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
rowSelection?: import("@tanstack/table-core").RowSelectionState;
|
||||
onRowSelectionChange?: (rowSelection: import("@tanstack/table-core").RowSelectionState) => void;
|
||||
onRowClick?: (row: TData) => void;
|
||||
onRowDoubleClick?: (row: TData) => void;
|
||||
};
|
||||
|
||||
let {
|
||||
@@ -30,7 +31,8 @@
|
||||
onSortingChange,
|
||||
rowSelection = {},
|
||||
onRowSelectionChange,
|
||||
onRowClick
|
||||
onRowClick,
|
||||
onRowDoubleClick
|
||||
}: DataTableProps<TData, TValue> = $props();
|
||||
|
||||
const table = createSvelteTable({
|
||||
@@ -170,7 +172,8 @@
|
||||
<Table.Row
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
onclick={() => onRowClick?.(row.original)}
|
||||
class={onRowClick ? "cursor-pointer" : ""}
|
||||
ondblclick={() => onRowDoubleClick?.(row.original)}
|
||||
class={(onRowClick || onRowDoubleClick) ? "cursor-pointer" : ""}
|
||||
>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
|
||||
@@ -186,6 +186,7 @@
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
onRowDoubleClick={(item) => goto(`/dashboard/general_catalogs/company_information/edit/${item.id}`)}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
|
||||
@@ -276,6 +276,7 @@
|
||||
{loadMore}
|
||||
{selectedId}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={(item) => goto(`/dashboard/general_catalogs/doda/edit/${item.id}`)}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
// Estado de la lista de clases
|
||||
let classes = $state<FixedAssetClassExtended[]>([]);
|
||||
let selectedClassIds = $state<number[]>([]);
|
||||
let selectedClass = $state<FixedAssetClassExtended | null>(null);
|
||||
let isLoading = $state(false);
|
||||
let searchTerm = $state('');
|
||||
@@ -107,6 +108,9 @@
|
||||
if (response.data) {
|
||||
// Data already comes with FA fields embedded
|
||||
classes = response.data as FixedAssetClassExtended[];
|
||||
handleSelectedIdsChange(
|
||||
selectedClassIds.filter((id) => classes.some((item) => item.id === id))
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error cargando clases:', error);
|
||||
@@ -116,7 +120,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
function selectClass(cls: A76Class) {
|
||||
function clearSelectedClass() {
|
||||
selectedClass = null;
|
||||
formData = {
|
||||
class_code: '',
|
||||
description_es: '',
|
||||
description_en: '',
|
||||
material_key: '',
|
||||
unit_of_measure: '',
|
||||
fraction: '',
|
||||
us_fraction: '',
|
||||
unit_measure_trade: '',
|
||||
depreciation_rate: null as number | null,
|
||||
fda_code: '',
|
||||
eccn_code: '',
|
||||
bom: ''
|
||||
};
|
||||
}
|
||||
|
||||
function setActiveClass(cls: FixedAssetClassExtended | null) {
|
||||
if (!cls) {
|
||||
clearSelectedClass();
|
||||
return;
|
||||
}
|
||||
|
||||
selectedClass = cls;
|
||||
formData = {
|
||||
class_code: cls.class_code,
|
||||
@@ -134,22 +161,27 @@
|
||||
};
|
||||
}
|
||||
|
||||
function handleSelectedIdsChange(ids: number[]) {
|
||||
selectedClassIds = ids;
|
||||
const primaryId = ids[0];
|
||||
const primaryClass = primaryId
|
||||
? classes.find((item) => item.id === primaryId) ?? null
|
||||
: null;
|
||||
|
||||
setActiveClass(primaryClass);
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(cls: A76Class) {
|
||||
const fixedClass = cls as FixedAssetClassExtended;
|
||||
selectedClassIds = [fixedClass.id, ...selectedClassIds.filter((id) => id !== fixedClass.id)];
|
||||
setActiveClass(fixedClass);
|
||||
validationError = '';
|
||||
showInsertDialog = true;
|
||||
}
|
||||
|
||||
function handleNew() {
|
||||
selectedClass = null;
|
||||
formData = {
|
||||
class_code: '',
|
||||
description_es: '',
|
||||
description_en: '',
|
||||
material_key: '',
|
||||
unit_of_measure: '',
|
||||
fraction: '',
|
||||
us_fraction: '',
|
||||
unit_measure_trade: '',
|
||||
depreciation_rate: null as number | null,
|
||||
fda_code: '',
|
||||
eccn_code: '',
|
||||
bom: ''
|
||||
};
|
||||
selectedClassIds = [];
|
||||
clearSelectedClass();
|
||||
}
|
||||
|
||||
async function handleRefresh() {
|
||||
@@ -185,22 +217,9 @@
|
||||
// Recargar lista
|
||||
await loadClasses();
|
||||
|
||||
selectedClass = null;
|
||||
selectedClassIds = [];
|
||||
clearSelectedClass();
|
||||
showDeleteDialog = false;
|
||||
formData = {
|
||||
class_code: '',
|
||||
description_es: '',
|
||||
description_en: '',
|
||||
material_key: '',
|
||||
unit_of_measure: '',
|
||||
fraction: '',
|
||||
us_fraction: '',
|
||||
unit_measure_trade: '',
|
||||
depreciation_rate: null as number | null,
|
||||
fda_code: '',
|
||||
eccn_code: '',
|
||||
bom: ''
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error deleting class:', error);
|
||||
toast.error('Error al eliminar la clase');
|
||||
@@ -318,8 +337,9 @@
|
||||
data={filteredClasses}
|
||||
{columns}
|
||||
loading={isLoading}
|
||||
selectedId={selectedClass?.id}
|
||||
onRowClick={selectClass}
|
||||
selectedIds={selectedClassIds}
|
||||
onSelectedIdsChange={handleSelectedIdsChange}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
{sorting}
|
||||
onSortingChange={(newSorting) => (sorting = newSorting)}
|
||||
/>
|
||||
@@ -610,7 +630,8 @@
|
||||
const wasUpdate = !!selectedClass?.id;
|
||||
await loadClasses();
|
||||
showInsertDialog = false;
|
||||
selectedClass = null;
|
||||
selectedClassIds = [];
|
||||
clearSelectedClass();
|
||||
validationError = '';
|
||||
toast.success(
|
||||
wasUpdate ? 'Clase actualizada correctamente' : 'Clase creada correctamente'
|
||||
|
||||
@@ -303,6 +303,7 @@
|
||||
{sorting}
|
||||
onSortingChange={(newSorting) => (sorting = newSorting)}
|
||||
onRowClick={selectPart}
|
||||
onRowDoubleClick={(part) => goto(`/dashboard/goods/parts/edit/${part.id}`)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user