diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 9c4ce0ae..02c77d3b 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -3,6 +3,7 @@ namespace App\Http\Middleware; use Illuminate\Http\Request; +use Inertia\Inertia; use Inertia\Middleware; use Tighten\Ziggy\Ziggy; @@ -53,6 +54,7 @@ public function share(Request $request): array 'error' => fn () => $request->session()->get('flash_error'), 'message' => fn () => $request->session()->get('flash_message'), ], + 'queryParams' => Inertia::always($request->query()), ]; } } diff --git a/package-lock.json b/package-lock.json index baa6ecc1..a1d6cb3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,6 @@ "@primevue/auto-import-resolver": "^4.3.3", "@tailwindcss/vite": "^4.0.17", "@types/lodash-es": "^4.17.12", - "@types/qs": "^6.9.18", "@vitejs/plugin-vue": "^5.2.3", "@vue/server-renderer": "^3.5.14", "@vueuse/core": "^13.0.0", @@ -20,7 +19,6 @@ "lodash-es": "^4.17.21", "lucide-vue-next": "^0.485.0", "primevue": "^4.3.3", - "qs": "^6.14.0", "tailwind-merge": "^3.2.0", "tailwindcss": "^4.0.17", "tailwindcss-primeui": "^0.6.1", diff --git a/package.json b/package.json index 273d8bb3..0bed9070 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ "@primevue/auto-import-resolver": "^4.3.3", "@tailwindcss/vite": "^4.0.17", "@types/lodash-es": "^4.17.12", - "@types/qs": "^6.9.18", "@vitejs/plugin-vue": "^5.2.3", "@vue/server-renderer": "^3.5.14", "@vueuse/core": "^13.0.0", @@ -22,7 +21,6 @@ "lodash-es": "^4.17.21", "lucide-vue-next": "^0.485.0", "primevue": "^4.3.3", - "qs": "^6.14.0", "tailwind-merge": "^3.2.0", "tailwindcss": "^4.0.17", "tailwindcss-primeui": "^0.6.1", diff --git a/resources/js/composables/usePaginatedData.ts b/resources/js/composables/usePaginatedData.ts index 16543291..2f72501d 100644 --- a/resources/js/composables/usePaginatedData.ts +++ b/resources/js/composables/usePaginatedData.ts @@ -1,18 +1,18 @@ import { ref, computed, onMounted, toRaw } from 'vue'; -import { router } from '@inertiajs/vue3'; +import { router, usePage } from '@inertiajs/vue3'; import type { Page, PageProps } from '@inertiajs/core'; import { FilterMatchMode } from '@primevue/core/api'; import { PageState, DataTablePageEvent } from 'primevue'; import debounce from 'lodash-es/debounce'; -import qs from 'qs'; -import type { PrimeVueDataFilters, InertiaRouterFetchCallbacks } from '@/types'; +import type { AppPageProps, PrimeVueDataFilters, InertiaRouterFetchCallbacks } from '@/types'; -interface PaginatedFilteredSortedQueryParams { +interface QueryParams { filters?: PrimeVueDataFilters; page?: string; rows?: string; sortField?: string; sortOrder?: string; + [key: string]: any; } interface PaginationState { page: number; @@ -22,13 +22,16 @@ interface SortState { field: string; order: number; } +type InertiaPageProps = PageProps & Omit & { + queryParams: QueryParams +} export function usePaginatedData( propDataToFetch: string | string[], initialFilters: PrimeVueDataFilters = {}, initialRows: number = 20 ) { - const urlParams = ref({}); + const page = usePage(); const processing = ref(false); const filters = ref(structuredClone(toRaw(initialFilters))); const sorting = ref({ @@ -43,9 +46,10 @@ export function usePaginatedData( const firstDatasetIndex = computed(() => { return (pagination.value.page - 1) * pagination.value.rows; }); + const filteredOrSorted = computed(() => { - const paramsFilters = urlParams.value?.filters || {}; - const sortField = urlParams.value?.sortField || null; + const paramsFilters = page.props.queryParams?.filters || {}; + const sortField = page.props.queryParams?.sortField || null; const isFiltering = Object.values(paramsFilters).some( (filter) => filter.value !== null && filter.value !== '' ); @@ -58,21 +62,6 @@ export function usePaginatedData( filterCallback(); }, 300); - function setUrlParams(): void { - const queryString = window.location.search; - const params = qs.parse(queryString, { - ignoreQueryPrefix: true, - strictNullHandling: true, - decoder: function (str, defaultDecoder) { - // set empty string values to null to match Laravel backend behavior - const value = defaultDecoder(str); - return value === '' ? null : value; - }, - }) as PaginatedFilteredSortedQueryParams; - - urlParams.value = { ...params }; - } - function scrollToTop(): void { window.scrollTo({ top: 0, @@ -85,7 +74,6 @@ export function usePaginatedData( return new Promise((resolve, reject) => { processing.value = true; - router.visit(window.location.pathname, { method: 'get', data: { @@ -98,7 +86,9 @@ export function usePaginatedData( preserveUrl: false, showProgress: true, replace: true, - only: Array.isArray(propDataToFetch) ? propDataToFetch : [propDataToFetch], + only: Array.isArray(propDataToFetch) + ? [...propDataToFetch, 'queryParams'] + : [propDataToFetch, 'queryParams'], onSuccess: (page) => { onSuccess?.(page); resolve(page); @@ -108,7 +98,6 @@ export function usePaginatedData( reject(errors); }, onFinish: () => { - setUrlParams(); processing.value = false; onFinish?.(); }, @@ -122,7 +111,6 @@ export function usePaginatedData( } else { pagination.value.page = event.page + 1; } - pagination.value.rows = event.rows; return fetchData({ @@ -134,8 +122,8 @@ export function usePaginatedData( function filter(options: InertiaRouterFetchCallbacks = {}): Promise> { const { onFinish: onFinishCallback, onSuccess, onError } = options; - pagination.value.page = 1; + return fetchData({ onSuccess, onError, @@ -162,22 +150,23 @@ export function usePaginatedData( return new Promise((resolve, reject) => { processing.value = true; - router.visit(window.location.pathname, { method: 'get', preserveUrl: false, showProgress: true, replace: true, - only: Array.isArray(propDataToFetch) ? propDataToFetch : [propDataToFetch], - onSuccess(page) { + only: Array.isArray(propDataToFetch) + ? [...propDataToFetch, 'queryParams'] + : [propDataToFetch, 'queryParams'], + onSuccess: (page) => { onSuccess?.(page); resolve(page); }, - onError(errors) { + onError: (errors) => { onError?.(errors); reject(errors); }, - onFinish() { + onFinish: () => { processing.value = false; onFinish?.(); }, @@ -188,19 +177,17 @@ export function usePaginatedData( function parseUrlFilterValues(): void { Object.keys(filters.value).forEach((key) => { const filter = filters.value[key]; - if (!filter?.value || !filter?.matchMode) { return; } - - if ( - filter.matchMode == FilterMatchMode.DATE_IS || - filter.matchMode == FilterMatchMode.DATE_IS_NOT || - filter.matchMode == FilterMatchMode.DATE_BEFORE || - filter.matchMode == FilterMatchMode.DATE_AFTER - ) { + if ([ + FilterMatchMode.DATE_IS, + FilterMatchMode.DATE_IS_NOT, + FilterMatchMode.DATE_BEFORE, + FilterMatchMode.DATE_AFTER, + ].includes(filter.matchMode)) { filters.value[key].value = new Date(filter.value as string); - } else if (filter.matchMode == FilterMatchMode.BETWEEN) { + } else if (filter.matchMode === FilterMatchMode.BETWEEN) { filter.value.forEach((value: any, index: number) => { if (typeof value === 'string') { filter.value[index] = new Date(value); @@ -214,7 +201,7 @@ export function usePaginatedData( filters.value[key].value = Number(filter.value); } else if ( Array.isArray(filter.value) || - filter.matchMode == FilterMatchMode.IN + filter.matchMode === FilterMatchMode.IN ) { if (filter.value.length === 0) { // empty arrays cause filtering issues, set to null instead @@ -222,7 +209,6 @@ export function usePaginatedData( } else { // Unique array values const unique = [...new Set(filter.value)]; - filter.value = unique; filter.value.forEach((value: any, index: number) => { if (typeof value === 'string' && !isNaN(Number(value))) { @@ -234,34 +220,29 @@ export function usePaginatedData( }); } - function parseUrlParams(urlParamsObj: PaginatedFilteredSortedQueryParams): void { + function parseUrlParams(): void { + const queryParams = page.props.queryParams || {}; filters.value = { ...structuredClone(toRaw(initialFilters)), - ...urlParamsObj?.filters, + ...queryParams.filters, }; - parseUrlFilterValues(); - - if (urlParamsObj?.sortField) { - sorting.value.field = urlParamsObj.sortField; + if (queryParams.sortField) { + sorting.value.field = queryParams.sortField; } - - if (urlParamsObj?.sortOrder) { - sorting.value.order = parseInt(urlParamsObj.sortOrder); + if (queryParams.sortOrder) { + sorting.value.order = parseInt(queryParams.sortOrder); } - - if (urlParamsObj?.page) { - pagination.value.page = parseInt(urlParamsObj.page); + if (queryParams.page) { + pagination.value.page = parseInt(queryParams.page); } - - if (urlParamsObj?.rows) { - pagination.value.rows = parseInt(urlParamsObj.rows); + if (queryParams.rows) { + pagination.value.rows = parseInt(queryParams.rows); } } onMounted(() => { - setUrlParams(); - parseUrlParams(urlParams.value); + parseUrlParams(); }); return { diff --git a/resources/js/types/global.d.ts b/resources/js/types/global.d.ts index 8c1f28b0..d7fe1021 100644 --- a/resources/js/types/global.d.ts +++ b/resources/js/types/global.d.ts @@ -1,7 +1,7 @@ import { PageProps as InertiaPageProps } from '@inertiajs/core'; import { AxiosInstance } from 'axios'; import { route as ziggyRoute } from 'ziggy-js'; -import { PageProps as AppPageProps } from './'; +import { AppPageProps } from './'; declare global { interface Window { diff --git a/resources/js/types/index.d.ts b/resources/js/types/index.d.ts index 16682831..954b2981 100644 --- a/resources/js/types/index.d.ts +++ b/resources/js/types/index.d.ts @@ -1,18 +1,35 @@ import type { DataTableFilterMetaData } from 'primevue'; -import type { Page, PageProps, Errors } from '@inertiajs/core'; +import type { Page, Errors } from '@inertiajs/core'; import type { MenuItem as PrimeVueMenuItem } from 'primevue/menuitem'; import type { LucideIcon } from 'lucide-vue-next'; +import type { Config } from 'ziggy-js'; export interface User { id: number; name: string; email: string; - email_verified_at?: string; + email_verified_at: string | null; } -export type PageProps< - T extends Record = Record -> = T; +export interface AuthProps { + user: User | null; +} + +export interface FlashProps { + success?: string | null; + info?: string | null; + warn?: string | null; + error?: string | null; + message?: string | null; +} + +export type AppPageProps = Record> = T & { + colorScheme: 'auto' | 'light' | 'dark'; + ziggy: Config & { location: string }; + auth: AuthProps; + flash: FlashProps; + queryParams: Record; +}; export type PrimeVueDataFilters = { [key: string]: DataTableFilterMetaData;