678 lines
23 KiB
TypeScript
678 lines
23 KiB
TypeScript
/**
|
|
* @license
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* TanStack Query hooks wrapping the Fiesta REST client in `./fiestaApi`.
|
|
*
|
|
* Components call these (never fetch directly) to get caching, dedup, and
|
|
* loading/error state. These power the operational pages (Inventory, Orders &
|
|
* Deliveries, Operations, Reports, Stores/Logistics/Staffing); the Dashboard
|
|
* continues to use the Hasura hooks in `./queries`.
|
|
*/
|
|
|
|
import { useQuery, useMutation, useQueryClient, useQueries } from '@tanstack/react-query';
|
|
import type { Row } from './fiestaApi';
|
|
import { loginRequest, matchTenantUser, buildAuthUser, type AuthUser } from './auth';
|
|
import {
|
|
FIESTA_TENANT_ID,
|
|
FIESTA_APPLOCATION_ID,
|
|
FIESTA_PRIMARY_LOCATION_ID,
|
|
getOrderSummary,
|
|
getRevenueSummary,
|
|
getTimeSeries,
|
|
getLocationSummary,
|
|
getOrderInsight,
|
|
getOrders,
|
|
getDeliverySummary,
|
|
getDeliveries,
|
|
getDeliveryInsight,
|
|
getDeliveryReport,
|
|
getFleetSummary,
|
|
getOrderDetails,
|
|
getCustomerOrders,
|
|
getRiders,
|
|
getRiderShifts,
|
|
getRiderPeriodicLogs,
|
|
getRiderLogs,
|
|
getBatchEfficiency,
|
|
updateDelivery,
|
|
reassignDeliveries,
|
|
getTenantLocations,
|
|
getAllTenants,
|
|
getTenantCustomers,
|
|
getStockStatement,
|
|
getProductsCount,
|
|
getProductStocks,
|
|
getProductLocations,
|
|
getMasterCatalog,
|
|
getProductCategories,
|
|
getProductSubcategories,
|
|
getAllUsers,
|
|
getUserById,
|
|
createUser,
|
|
updateUser,
|
|
assignRiderToOrders,
|
|
CreateUserInput,
|
|
createTenantUser,
|
|
createTenantLocation,
|
|
CreateTenantInput,
|
|
CreateTenantLocationInput,
|
|
} from './fiestaApi';
|
|
|
|
export const fiestaKeys = {
|
|
orderSummary: (tenantid: number, fromdate: string, todate: string, locationid?: number) =>
|
|
['fiesta', 'orderSummary', tenantid, fromdate, todate, locationid ?? 0] as const,
|
|
revenueSummary: (params: Record<string, unknown>) => ['fiesta', 'revenueSummary', params] as const,
|
|
timeSeries: (params: Record<string, unknown>) => ['fiesta', 'timeSeries', params] as const,
|
|
locationSummary: (tenantid: number) => ['fiesta', 'locationSummary', tenantid] as const,
|
|
orderInsight: (tenantid: number) => ['fiesta', 'orderInsight', tenantid] as const,
|
|
orders: (params: Record<string, unknown>) => ['fiesta', 'orders', params] as const,
|
|
deliverySummary: (params: Record<string, unknown>) => ['fiesta', 'deliverySummary', params] as const,
|
|
deliveries: (params: Record<string, unknown>) => ['fiesta', 'deliveries', params] as const,
|
|
deliveryInsight: (tenantid: number) => ['fiesta', 'deliveryInsight', tenantid] as const,
|
|
riders: (params: Record<string, unknown>) => ['fiesta', 'riders', params] as const,
|
|
riderShifts: (applocationid: number) => ['fiesta', 'riderShifts', applocationid] as const,
|
|
riderPeriodicLogs: (params: Record<string, unknown>) => ['fiesta', 'riderPeriodicLogs', params] as const,
|
|
riderLogs: (params: Record<string, unknown>) => ['fiesta', 'riderLogs', params] as const,
|
|
batchEfficiency: (params: Record<string, unknown>) => ['fiesta', 'batchEfficiency', params] as const,
|
|
// v2: bumped when test-row filtering was added to getTenantLocations so any
|
|
// warm cache holding the old unfiltered (duplicated/junk) rows is bypassed.
|
|
tenantLocations: (tenantid: number) => ['fiesta', 'tenantLocations', 'v2', tenantid] as const,
|
|
allTenants: (params: Record<string, unknown>) => ['fiesta', 'allTenants', params] as const,
|
|
tenantCustomers: (params: Record<string, unknown>) => ['fiesta', 'tenantCustomers', params] as const,
|
|
stockStatement: (params: Record<string, unknown>) => ['fiesta', 'stockStatement', params] as const,
|
|
productsCount: (params: Record<string, unknown>) => ['fiesta', 'productsCount', params] as const,
|
|
productStocks: (params: Record<string, unknown>) => ['fiesta', 'productStocks', params] as const,
|
|
productLocations: (params: Record<string, unknown>) => ['fiesta', 'productLocations', params] as const,
|
|
masterCatalog: (params: Record<string, unknown>) => ['fiesta', 'masterCatalog', params] as const,
|
|
productCategories: () => ['fiesta', 'productCategories'] as const,
|
|
productSubcategories: (params: Record<string, unknown>) => ['fiesta', 'productSubcategories', params] as const,
|
|
orderDetails: (orderheaderid: number | string) => ['fiesta', 'orderDetails', orderheaderid] as const,
|
|
customerOrders: (params: Record<string, unknown>) => ['fiesta', 'customerOrders', params] as const,
|
|
deliveryReport: (params: Record<string, unknown>) => ['fiesta', 'deliveryReport', params] as const,
|
|
fleetSummary: (params: Record<string, unknown>) => ['fiesta', 'fleetSummary', params] as const,
|
|
users: (params: Record<string, unknown>) => ['fiesta', 'users', params] as const,
|
|
user: (userid: number) => ['fiesta', 'user', userid] as const,
|
|
};
|
|
|
|
// ── Orders ──────────────────────────────────────────────────────────────────
|
|
export function useFiestaOrderSummary(tenantid: number = FIESTA_TENANT_ID, fromdate: string, todate: string, locationid?: number) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.orderSummary(tenantid, fromdate, todate, locationid),
|
|
queryFn: () => getOrderSummary(tenantid, fromdate, todate, locationid),
|
|
enabled: Boolean(tenantid && fromdate && todate),
|
|
});
|
|
}
|
|
|
|
export function useFiestaRevenueSummary(opts: {
|
|
tenantid: number;
|
|
fromdate: string;
|
|
todate: string;
|
|
locationid?: number;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.revenueSummary(opts as Record<string, unknown>),
|
|
queryFn: () => getRevenueSummary(opts),
|
|
enabled: Boolean(opts.tenantid && opts.fromdate && opts.todate),
|
|
});
|
|
}
|
|
|
|
export function useFiestaTimeSeries(opts: {
|
|
tenantid: number;
|
|
granularity: 'day' | 'month' | 'year';
|
|
fromdate: string;
|
|
todate: string;
|
|
locationid?: number;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.timeSeries(opts as Record<string, unknown>),
|
|
queryFn: () => getTimeSeries(opts),
|
|
enabled: Boolean(opts.tenantid && opts.fromdate && opts.todate),
|
|
});
|
|
}
|
|
|
|
export function useFiestaLocationSummary(tenantid: number = FIESTA_TENANT_ID) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.locationSummary(tenantid),
|
|
queryFn: () => getLocationSummary(tenantid),
|
|
enabled: Boolean(tenantid),
|
|
});
|
|
}
|
|
|
|
export function useFiestaOrderInsight(tenantid: number = FIESTA_TENANT_ID) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.orderInsight(tenantid),
|
|
queryFn: () => getOrderInsight(tenantid),
|
|
enabled: Boolean(tenantid),
|
|
});
|
|
}
|
|
|
|
export function useFiestaOrders(opts: {
|
|
tenantid: number;
|
|
status: string;
|
|
fromdate: string;
|
|
todate: string;
|
|
locationid?: number;
|
|
applocationid?: number;
|
|
keyword?: string;
|
|
pageno?: number;
|
|
pagesize?: number;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.orders(opts),
|
|
queryFn: () => getOrders(opts),
|
|
enabled: Boolean(opts.tenantid && opts.status && opts.fromdate && opts.todate),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Fetches orders across all statuses for a given date range by firing one
|
|
* request per status in parallel and merging the results. This is needed
|
|
* because the /orders/getorders API requires an explicit status param and
|
|
* returns an empty array when status is blank or 'all'.
|
|
*/
|
|
export function useFiestaAllOrders(opts: {
|
|
tenantid: number;
|
|
fromdate: string;
|
|
todate: string;
|
|
locationid?: number;
|
|
applocationid?: number;
|
|
keyword?: string;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: ['fiesta', 'allOrders', opts],
|
|
queryFn: async () => {
|
|
// Include all known statuses from ORDER_STATUS_MAP to ensure we don't miss orders
|
|
const statuses = [
|
|
'created', 'pending', 'processing', 'delivered', 'cancelled',
|
|
'accepted', 'assigned', 'ready', 'picked', 'active', 'arrived'
|
|
];
|
|
// Fetch sequentially to avoid rate-limiting or proxy dropping parallel requests
|
|
const results: Row[][] = [];
|
|
for (const status of statuses) {
|
|
try {
|
|
const res = await getOrders({
|
|
tenantid: opts.tenantid,
|
|
status,
|
|
fromdate: opts.fromdate,
|
|
todate: opts.todate,
|
|
locationid: opts.locationid,
|
|
applocationid: opts.applocationid,
|
|
keyword: opts.keyword,
|
|
pagesize: 500,
|
|
});
|
|
results.push(res);
|
|
} catch (e) {
|
|
results.push([]);
|
|
}
|
|
}
|
|
// Merge and deduplicate by orderid/orderheaderid
|
|
const merged: Row[] = [];
|
|
const seen = new Set<string>();
|
|
for (const list of results) {
|
|
for (const row of list) {
|
|
const id = String(row.orderid || row.orderheaderid || Math.random());
|
|
if (!seen.has(id)) {
|
|
seen.add(id);
|
|
merged.push(row);
|
|
}
|
|
}
|
|
}
|
|
return merged;
|
|
},
|
|
enabled: Boolean(opts.tenantid && opts.fromdate && opts.todate),
|
|
});
|
|
}
|
|
|
|
// ── Deliveries ────────────────────────────────────────────────────────────────
|
|
export function useFiestaDeliverySummary(opts: {
|
|
tenantid: number;
|
|
applocationid?: number;
|
|
locationid?: number;
|
|
fromdate: string;
|
|
todate: string;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.deliverySummary(opts),
|
|
queryFn: () => getDeliverySummary(opts),
|
|
enabled: Boolean(opts.tenantid && opts.fromdate && opts.todate),
|
|
});
|
|
}
|
|
|
|
export function useFiestaDeliveries(opts: {
|
|
tenantid: number;
|
|
fromdate: string;
|
|
todate: string;
|
|
status?: string;
|
|
locationid?: number;
|
|
applocationid?: number;
|
|
keyword?: string;
|
|
pageno?: number;
|
|
pagesize?: number;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.deliveries(opts),
|
|
queryFn: () => getDeliveries(opts),
|
|
enabled: Boolean(opts.tenantid && opts.fromdate && opts.todate),
|
|
});
|
|
}
|
|
|
|
export function useFiestaDeliveryInsight(tenantid: number = FIESTA_TENANT_ID) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.deliveryInsight(tenantid),
|
|
queryFn: () => getDeliveryInsight(tenantid),
|
|
enabled: Boolean(tenantid),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Bulk-assign one rider to many orders (the Orders board's multi-select assign).
|
|
* Fires one updatedelivery per row in parallel, tolerates partial failure, and
|
|
* refreshes the orders + deliveries lists on completion.
|
|
*/
|
|
export function useFiestaAssignRider() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (input: { userid: number; orders: Row[] }) => assignRiderToOrders(input.userid, input.orders),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['fiesta', 'orders'] });
|
|
qc.invalidateQueries({ queryKey: ['fiesta', 'orderSummary'] });
|
|
// Refresh the Deliveries board AND its KPI summary so a freshly-assigned
|
|
// order shows up on the deliveries page immediately (table + count cards).
|
|
qc.invalidateQueries({ queryKey: ['fiesta', 'deliveries'] });
|
|
qc.invalidateQueries({ queryKey: ['fiesta', 'deliverySummary'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
// ── Partners / Riders ─────────────────────────────────────────────────────────
|
|
export function useFiestaRiders(opts: { applocationid?: number; tenantid: number; partnerid?: number }) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.riders(opts),
|
|
queryFn: () => getRiders(opts),
|
|
enabled: Boolean(opts.tenantid),
|
|
});
|
|
}
|
|
|
|
export function useFiestaRiderShifts(applocationid: number = FIESTA_APPLOCATION_ID) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.riderShifts(applocationid),
|
|
queryFn: () => getRiderShifts(applocationid),
|
|
enabled: Boolean(applocationid),
|
|
});
|
|
}
|
|
|
|
// ── Dispatch / Telemetry ─────────────────────────────────────────────────────
|
|
export function useFiestaRiderPeriodicLogs(opts: {
|
|
userid?: number;
|
|
riderid?: number;
|
|
fromdate: string;
|
|
todate: string;
|
|
tenantid?: number;
|
|
applocationid?: number;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.riderPeriodicLogs(opts),
|
|
queryFn: () => getRiderPeriodicLogs(opts),
|
|
enabled: Boolean(opts.fromdate && opts.todate),
|
|
});
|
|
}
|
|
|
|
export function useFiestaRiderLogs(opts: {
|
|
userid?: number;
|
|
riderid?: number;
|
|
fromdate: string;
|
|
todate: string;
|
|
tenantid?: number;
|
|
applocationid?: number;
|
|
pageno?: number;
|
|
pagesize?: number;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.riderLogs(opts),
|
|
queryFn: () => getRiderLogs(opts),
|
|
enabled: Boolean(opts.fromdate && opts.todate),
|
|
});
|
|
}
|
|
|
|
export function useFiestaBatchEfficiency(opts: {
|
|
partnerid?: number;
|
|
tenantid: number;
|
|
fromdate: string;
|
|
todate: string;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.batchEfficiency(opts),
|
|
queryFn: () => getBatchEfficiency(opts),
|
|
enabled: Boolean(opts.tenantid && opts.fromdate && opts.todate),
|
|
});
|
|
}
|
|
|
|
export function useFiestaUpdateDelivery() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (input: { deliveryid: number; updates: Row }) =>
|
|
updateDelivery(input.deliveryid, input.updates),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['fiesta', 'deliveries'] });
|
|
qc.invalidateQueries({ queryKey: ['fiesta', 'deliverySummary'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useFiestaReassignDeliveries() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (input: { userid: number; deliveryids: number[] }) =>
|
|
reassignDeliveries(input),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['fiesta', 'deliveries'] });
|
|
qc.invalidateQueries({ queryKey: ['fiesta', 'deliverySummary'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
// ── Tenants / Customers ─────────────────────────────────────────────────────────
|
|
export function useFiestaTenantLocations(tenantid: number = FIESTA_TENANT_ID) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.tenantLocations(tenantid),
|
|
queryFn: () => getTenantLocations(tenantid),
|
|
enabled: Boolean(tenantid),
|
|
});
|
|
}
|
|
|
|
export function useFiestaAllTenants(opts: {
|
|
applocationid?: number;
|
|
status?: string;
|
|
pageno?: number;
|
|
pagesize?: number;
|
|
} = {}) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.allTenants(opts),
|
|
queryFn: () => getAllTenants(opts),
|
|
});
|
|
}
|
|
|
|
export function useFiestaTenantCustomers(opts: {
|
|
tenantid: number;
|
|
locationid: number;
|
|
keyword?: string;
|
|
pageno?: number;
|
|
pagesize?: number;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.tenantCustomers(opts),
|
|
queryFn: () => getTenantCustomers(opts),
|
|
enabled: Boolean(opts.tenantid && opts.locationid),
|
|
});
|
|
}
|
|
|
|
// ── Products / Stock ─────────────────────────────────────────────────────────
|
|
export function useFiestaStockStatement(opts: {
|
|
tenantid: number;
|
|
locationid: number;
|
|
subcategoryid?: number;
|
|
keyword?: string;
|
|
pageno?: number;
|
|
pagesize?: number;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.stockStatement(opts),
|
|
queryFn: () => getStockStatement(opts),
|
|
enabled: Boolean(opts.tenantid && opts.locationid),
|
|
});
|
|
}
|
|
|
|
export function useFiestaProductsCount(opts: { tenantid: number; categoryid: number; subcategoryid?: number }) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.productsCount(opts),
|
|
queryFn: () => getProductsCount(opts),
|
|
enabled: Boolean(opts.tenantid && opts.categoryid),
|
|
});
|
|
}
|
|
|
|
export interface StoreStock {
|
|
locationid: number;
|
|
locationname: string;
|
|
isLoading: boolean;
|
|
isError: boolean;
|
|
rows: Row[];
|
|
}
|
|
|
|
/**
|
|
* Live stock statement for every outlet under the tenant — powers the admin's
|
|
* "all stores' stock" view. One query per location (deduped/cached by React
|
|
* Query); the returned array stays aligned with the `locations` input.
|
|
*/
|
|
export function useFiestaStoresStock(
|
|
tenantid: number,
|
|
locations: Array<{ locationid: number; locationname: string }>,
|
|
): StoreStock[] {
|
|
const results = useQueries({
|
|
queries: locations.map((loc) => ({
|
|
queryKey: fiestaKeys.stockStatement({ scope: 'stores', tenantid, locationid: loc.locationid }),
|
|
queryFn: () => getStockStatement({ tenantid, locationid: loc.locationid, pagesize: 200 }),
|
|
enabled: Boolean(tenantid && loc.locationid),
|
|
})),
|
|
});
|
|
return locations.map((loc, i) => ({
|
|
locationid: loc.locationid,
|
|
locationname: loc.locationname,
|
|
isLoading: results[i]?.isLoading ?? true,
|
|
isError: results[i]?.isError ?? false,
|
|
rows: (results[i]?.data as Row[]) ?? [],
|
|
}));
|
|
}
|
|
|
|
// ── Order details / customer history ───────────────────────────────────────────
|
|
export function useFiestaOrderDetails(orderheaderid: number | string | null | undefined) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.orderDetails(orderheaderid ?? ''),
|
|
queryFn: () => getOrderDetails(orderheaderid as number | string),
|
|
enabled: Boolean(orderheaderid),
|
|
});
|
|
}
|
|
|
|
export function useFiestaCustomerOrders(opts: {
|
|
customerid: number | string | null | undefined;
|
|
status?: string;
|
|
pageno?: number;
|
|
pagesize?: number;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.customerOrders(opts as Record<string, unknown>),
|
|
queryFn: () =>
|
|
getCustomerOrders({
|
|
customerid: opts.customerid as number | string,
|
|
status: opts.status,
|
|
pageno: opts.pageno,
|
|
pagesize: opts.pagesize,
|
|
}),
|
|
enabled: Boolean(opts.customerid),
|
|
});
|
|
}
|
|
|
|
// ── Deliveries report / fleet ───────────────────────────────────────────────────
|
|
export function useFiestaDeliveryReport(opts: {
|
|
tenantid: number;
|
|
applocationid?: number;
|
|
partnerid?: number;
|
|
userid?: number;
|
|
fromdate: string;
|
|
todate: string;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.deliveryReport(opts),
|
|
queryFn: () => getDeliveryReport(opts),
|
|
enabled: Boolean(opts.tenantid && opts.fromdate && opts.todate),
|
|
});
|
|
}
|
|
|
|
export function useFiestaFleetSummary(opts: {
|
|
tenantid: number;
|
|
applocationid?: number;
|
|
partnerid?: number;
|
|
fromdate: string;
|
|
todate: string;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.fleetSummary(opts),
|
|
queryFn: () => getFleetSummary(opts),
|
|
enabled: Boolean(opts.tenantid && opts.fromdate && opts.todate),
|
|
});
|
|
}
|
|
|
|
// ── Products: live stocks / catalog / categories ────────────────────────────────
|
|
export function useFiestaProductStocks(opts: { tenantid: number; locationid: number }) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.productStocks(opts),
|
|
queryFn: () => getProductStocks(opts),
|
|
enabled: Boolean(opts.tenantid && opts.locationid),
|
|
});
|
|
}
|
|
|
|
export function useFiestaProductLocations(opts: {
|
|
tenantid: number;
|
|
locationid: number;
|
|
subcategoryid?: number;
|
|
pageno?: number;
|
|
pagesize?: number;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.productLocations(opts),
|
|
queryFn: () => getProductLocations(opts),
|
|
enabled: Boolean(opts.tenantid && opts.locationid),
|
|
});
|
|
}
|
|
|
|
export function useFiestaMasterCatalog(opts: {
|
|
tenantid: number;
|
|
locationid?: number;
|
|
subcategoryid?: number;
|
|
keyword?: string;
|
|
pageno?: number;
|
|
pagesize?: number;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.masterCatalog(opts),
|
|
queryFn: () => getMasterCatalog(opts),
|
|
enabled: Boolean(opts.tenantid),
|
|
});
|
|
}
|
|
|
|
export function useFiestaProductCategories() {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.productCategories(),
|
|
queryFn: () => getProductCategories(),
|
|
});
|
|
}
|
|
|
|
export function useFiestaProductSubcategories(opts: { categoryid: number; tenantid?: number }) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.productSubcategories(opts),
|
|
queryFn: () => getProductSubcategories(opts),
|
|
enabled: Boolean(opts.categoryid),
|
|
});
|
|
}
|
|
|
|
// ── Users ─────────────────────────────────────────────────────────────────────
|
|
export function useFiestaUsers(opts: {
|
|
tenantid: number;
|
|
roleid?: number;
|
|
keyword?: string;
|
|
pageno?: number;
|
|
pagesize?: number;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.users(opts),
|
|
queryFn: () => getAllUsers(opts),
|
|
enabled: Boolean(opts.tenantid),
|
|
});
|
|
}
|
|
|
|
export function useFiestaUser(userid: number) {
|
|
return useQuery({
|
|
queryKey: fiestaKeys.user(userid),
|
|
queryFn: () => getUserById(userid),
|
|
enabled: Boolean(userid),
|
|
});
|
|
}
|
|
|
|
/** Create a user, then refresh every users list on success. */
|
|
export function useFiestaCreateUser() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (input: CreateUserInput) => createUser(input),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['fiesta', 'users'] }),
|
|
});
|
|
}
|
|
|
|
/** Update a user, then refresh every users list on success. */
|
|
export function useFiestaUpdateUser() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (input: Parameters<typeof updateUser>[0]) => updateUser(input),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['fiesta', 'users'] }),
|
|
});
|
|
}
|
|
|
|
/** Create a new tenant and admin user, then refresh tenants list on success. */
|
|
export function useFiestaCreateTenant() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (input: CreateTenantInput) => createTenantUser(input),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['fiesta', 'allTenants'] }),
|
|
});
|
|
}
|
|
|
|
/** Create a new tenant location, then refresh tenant locations list on success. */
|
|
export function useFiestaCreateLocation() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (input: CreateTenantLocationInput) => createTenantLocation(input),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['fiesta', 'tenantLocations'] });
|
|
qc.invalidateQueries({ queryKey: ['fiesta', 'locationSummary'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
|
|
// ── Auth ──────────────────────────────────────────────────────────────────────
|
|
/**
|
|
* Verify login credentials against the Fiesta web-login endpoint. A mutation
|
|
* (not a query) since it's a POST with side effects; the form drives it via
|
|
* `mutate`/`mutateAsync` and reads `isPending`/`error` for loading + error UI.
|
|
*
|
|
* Both network calls go through React Query: the login POST is the mutation,
|
|
* and the role-resolution fallback (when the login response omits the role) is
|
|
* fetched via the query client — so it shares the Users-panel cache.
|
|
*/
|
|
export function useLogin() {
|
|
const qc = useQueryClient();
|
|
return useMutation<AuthUser, Error, { email: string; password: string }>({
|
|
mutationFn: async ({ email, password }) => {
|
|
const result = await loginRequest(email, password);
|
|
let row = result.row;
|
|
|
|
// The login response didn't carry a role — resolve it from the tenant user
|
|
// list through the query cache (deduped with useFiestaUsers).
|
|
if (!result.hasRole) {
|
|
const params = { tenantid: FIESTA_TENANT_ID, keyword: result.email, pagesize: 50 };
|
|
const users = await qc.fetchQuery({
|
|
queryKey: fiestaKeys.users(params),
|
|
queryFn: () => getAllUsers(params),
|
|
});
|
|
const match = matchTenantUser(users, result.email);
|
|
if (match) row = { ...match, ...(row ?? {}) };
|
|
}
|
|
|
|
return buildAuthUser(row, result.email);
|
|
},
|
|
});
|
|
}
|
|
|
|
export { FIESTA_TENANT_ID, FIESTA_APPLOCATION_ID, FIESTA_PRIMARY_LOCATION_ID };
|