From 765e74d8a9d41d3bfa803d82f65893df7850eb96 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Tue, 7 Jul 2026 22:41:55 +0700 Subject: [PATCH] feat: add loading state and enhance media thumbnail functionality in data tables for improved user experience --- .../js/components/data-table/DataTable.vue | 12 +++++++++- .../components/media/MediaThumbnailCell.vue | 3 ++- resources/js/composables/useDataTableQuery.ts | 11 +++++++++ .../products/table/ProductGroupedTable.vue | 6 ++++- .../admin/master/raw-materials/Index.vue | 3 ++- .../table/RawMaterialGroupedTable.vue | 23 +++++++++++++++---- 6 files changed, 50 insertions(+), 8 deletions(-) diff --git a/resources/js/components/data-table/DataTable.vue b/resources/js/components/data-table/DataTable.vue index c9021b0..9fb4f44 100644 --- a/resources/js/components/data-table/DataTable.vue +++ b/resources/js/components/data-table/DataTable.vue @@ -37,11 +37,13 @@ const props = withDefaults( paginationDisplayedCount?: number; paginationItemLabel?: string; getRowClassName?: (row: TData, index: number) => string | undefined; + loading?: boolean; }>(), { searchPlaceholder: 'Ketikkan sesuatu...', showRowNumber: true, paginationItemLabel: 'data', + loading: false, } ); @@ -147,7 +149,15 @@ function resolveRowSpan(row: TData, columnId: string): number | undefined { :search-placeholder="searchPlaceholder" @filter-change="(key, value) => emit('filter-change', key, value)" @filters-reset="emit('filters-reset')" /> -
+
+ +
+
+ + Memuat data... +
+
+ diff --git a/resources/js/components/media/MediaThumbnailCell.vue b/resources/js/components/media/MediaThumbnailCell.vue index d005597..ad54a67 100644 --- a/resources/js/components/media/MediaThumbnailCell.vue +++ b/resources/js/components/media/MediaThumbnailCell.vue @@ -13,6 +13,7 @@ const props = defineProps<{ items: MediaItem[]; maxVisible?: number; title?: string; + allUrls?: string[]; }>(); const previewOpen = ref(false); @@ -119,5 +120,5 @@ function openGallery() { - + diff --git a/resources/js/composables/useDataTableQuery.ts b/resources/js/composables/useDataTableQuery.ts index cf93a82..52337e2 100644 --- a/resources/js/composables/useDataTableQuery.ts +++ b/resources/js/composables/useDataTableQuery.ts @@ -9,6 +9,7 @@ type UseDataTableQueryOptions = { initial: DataTableQuery; filterKeys?: string[]; debounceMs?: number; + only?: string[]; }; function buildParams(query: DataTableQuery, filterKeys: string[]): Record { @@ -28,12 +29,21 @@ function buildParams(query: DataTableQuery, filterKeys: string[]): Record({ ...options.initial }); + const isLoading = ref(false); const reload = useDebounceFn(() => { router.get(options.url, buildParams(query.value, filterKeys), { preserveState: true, replace: true, preserveScroll: true, + showProgress: false, + only: options.only, + onBefore: () => { + isLoading.value = true; + }, + onFinish: () => { + isLoading.value = false; + }, }); }, options.debounceMs ?? 300); @@ -67,6 +77,7 @@ export function useDataTableQuery(options: UseDataTableQueryOptions) { return { query, + isLoading, setSearch, setSort, setFilter, diff --git a/resources/js/pages/admin/master/products/table/ProductGroupedTable.vue b/resources/js/pages/admin/master/products/table/ProductGroupedTable.vue index bce6645..d978407 100644 --- a/resources/js/pages/admin/master/products/table/ProductGroupedTable.vue +++ b/resources/js/pages/admin/master/products/table/ProductGroupedTable.vue @@ -55,6 +55,10 @@ function rowNumber(index: number): number { return groupedTableRowNumber(props.firstItem, index); } +function allVariantImageUrls(product: ProductListItem): string[] { + return product.variants.flatMap((variant) => (variant.images ?? []).map((img) => img.url)); +} + const verificationModalOpen = ref(false); const selectedRequestId = ref(null); @@ -148,7 +152,7 @@ function openVerificationDetail(requestId: number | undefined) { {{ variant.name }} - + {{ variant.stock_formatted }} diff --git a/resources/js/pages/admin/master/raw-materials/Index.vue b/resources/js/pages/admin/master/raw-materials/Index.vue index 6cd745f..bdced15 100644 --- a/resources/js/pages/admin/master/raw-materials/Index.vue +++ b/resources/js/pages/admin/master/raw-materials/Index.vue @@ -32,7 +32,7 @@ const props = defineProps<{ const { can } = useCan(); const search = ref(props.filters.search ?? ''); -const { query, setSearch, setFilter, resetFilters, syncFromServer } = +const { query, isLoading, setSearch, setFilter, resetFilters, syncFromServer } = useDataTableQuery({ url: index.url(), initial: { ...props.filters }, @@ -129,6 +129,7 @@ watch( :pagination-links="rawMaterials.links" :filter-defs="filterDefs" :filter-values="filterValues" + :loading="isLoading" @filter-change="setFilter" @filters-reset="resetFilters" /> diff --git a/resources/js/pages/admin/master/raw-materials/table/RawMaterialGroupedTable.vue b/resources/js/pages/admin/master/raw-materials/table/RawMaterialGroupedTable.vue index 5e42fd5..5ca4deb 100644 --- a/resources/js/pages/admin/master/raw-materials/table/RawMaterialGroupedTable.vue +++ b/resources/js/pages/admin/master/raw-materials/table/RawMaterialGroupedTable.vue @@ -32,6 +32,7 @@ const props = defineProps<{ paginationLinks?: DataTablePaginationLink[]; filterDefs?: DataTableFilterDef[]; filterValues?: Record; + loading?: boolean; }>(); const search = defineModel('search', { default: '' }); @@ -48,6 +49,10 @@ function rowNumber(index: number): number { return groupedTableRowNumber(props.firstItem, index); } +function allVariantImageUrls(material: RawMaterialListItem): string[] { + return material.prices.flatMap((price) => (price.images ?? []).map((img) => img.url)); +} + const verificationModalOpen = ref(false); const selectedRequestId = ref(null); @@ -64,8 +69,17 @@ function openVerificationDetail(requestId: number | undefined) { -
-
+
+ +
+
+ + Memuat data... +
+
+ +
+
@@ -130,7 +144,7 @@ function openVerificationDetail(requestId: number | undefined) { {{ price.variant }} - + {{ price.stock_formatted }} @@ -144,7 +158,8 @@ function openVerificationDetail(requestId: number | undefined) {
- + +