Intercity load unload with grn ,Warehouse , fleet , and allocation endpoints permission

This commit is contained in:
Hagernesh
2026-07-24 11:50:25 +00:00
parent cf8a2e928d
commit 2f124bc666
22 changed files with 827 additions and 99 deletions

View File

@@ -0,0 +1,96 @@
import { Button, Group, TextInput } from "@mantine/core";
import { DatePickerInput } from "@mantine/dates";
import { Search, X } from "lucide-react";
import type { ReactNode } from "react";
export interface ListControlsProps {
search: string;
onSearchChange: (value: string) => void;
searchPlaceholder?: string;
/** `YYYY-MM-DD`, matching Mantine 9's date inputs. */
dateFrom: string | null;
onDateFromChange: (value: string | null) => void;
dateTo: string | null;
onDateToChange: (value: string | null) => void;
/** Label above the range, naming the date being filtered (e.g. "Arrival date"). */
dateLabel?: string;
hasFilters?: boolean;
onReset?: () => void;
/** Page-specific selects (status, warehouse…) rendered after the date range. */
children?: ReactNode;
showSearch?: boolean;
showDateRange?: boolean;
}
/**
* Search box + inclusive date range + clear, shared by every freight list so the
* controls sit in the same place and behave the same way on all of them.
* Pair with `useListControls`, which owns the state and does the filtering.
*/
const ListControls = ({
search,
onSearchChange,
searchPlaceholder = "Search…",
dateFrom,
onDateFromChange,
dateTo,
onDateToChange,
dateLabel,
hasFilters,
onReset,
children,
showSearch = true,
showDateRange = true,
}: ListControlsProps) => (
<Group gap="sm" align="flex-end" wrap="wrap">
{showSearch && (
<TextInput
placeholder={searchPlaceholder}
value={search}
onChange={(e) => onSearchChange(e.currentTarget.value)}
leftSection={<Search size={16} />}
style={{ flex: "1 1 240px", minWidth: 200 }}
/>
)}
{showDateRange && (
<>
<DatePickerInput
label={dateLabel ? `${dateLabel} from` : "From"}
placeholder="Any"
value={dateFrom}
onChange={onDateFromChange}
// Cannot start after it ends — the picker refuses the invalid range
// instead of silently returning nothing.
maxDate={dateTo ?? undefined}
clearable
w={150}
/>
<DatePickerInput
label={dateLabel ? `${dateLabel} to` : "To"}
placeholder="Any"
value={dateTo}
onChange={onDateToChange}
minDate={dateFrom ?? undefined}
clearable
w={150}
/>
</>
)}
{children}
{hasFilters && onReset && (
<Button
variant="subtle"
color="gray"
leftSection={<X size={14} />}
onClick={onReset}
>
Clear
</Button>
)}
</Group>
);
export default ListControls;

View File

@@ -18,6 +18,11 @@ import { LoadInventoryModal } from './LoadInventoryModal';
import { MoveInventoryModal } from './MoveInventoryModal';
import { ReleaseOrderModal } from './ReleaseOrderModal';
import { WarehouseInventoryTable } from './WarehouseInventoryTable';
import ListControls from '@/components/common/ListControls';
// Generic list footer — already shared by the fleet and train-scheduling lists
// despite the ruleEngine path; reused here rather than adding a second one.
import RuleEngineListFooter from '@/components/ruleEngine/RuleEngineListFooter';
import { useListControls } from '@/hooks/useListControls';
import { extractDownloadErrorMessage, extractErrorMessage } from './options';
import { openPdfBlob, saveBlob } from './pdf';
@@ -56,8 +61,17 @@ export function InventoryWorkbench({ items, isLoading, onLastMile }: InventoryWo
api.warehouses.bulkMarkInspected.mutationOptions(),
);
const controls = useListControls(items, {
searchKeys: ['grnNumber', 'bookingReference', 'customerName', 'status', 'releaseOrderReference', 'notes'],
dateKey: 'arrivedAt',
});
const visible = controls.filteredRows;
const [selected, setSelected] = useState<Set<string>>(new Set());
const allSelected = items.length > 0 && selected.size === items.length;
// Select-all spans everything matching the current filters, not just the rows
// on screen — bulk "mark inspected" over one page of a filtered set would be a
// surprise. Counts compare against the filtered set for the same reason.
const allSelected = visible.length > 0 && selected.size === visible.length;
const someSelected = selected.size > 0 && !allSelected;
const toggleSelect = (id: string) =>
setSelected((prev) => {
@@ -66,7 +80,7 @@ export function InventoryWorkbench({ items, isLoading, onLastMile }: InventoryWo
return next;
});
const toggleSelectAll = () =>
setSelected(allSelected ? new Set() : new Set(items.map((i) => i.id)));
setSelected(allSelected ? new Set() : new Set(visible.map((i) => i.id)));
const markInspected = async () => {
if (selected.size === 0) {
@@ -268,8 +282,21 @@ export function InventoryWorkbench({ items, isLoading, onLastMile }: InventoryWo
</Button>
</Group>
<ListControls
search={controls.search}
onSearchChange={controls.setSearch}
searchPlaceholder="GRN, container, booking, customer…"
dateFrom={controls.dateFrom}
onDateFromChange={controls.setDateFrom}
dateTo={controls.dateTo}
onDateToChange={controls.setDateTo}
dateLabel="Arrived"
hasFilters={controls.hasFilters}
onReset={controls.reset}
/>
<WarehouseInventoryTable
items={items}
items={controls.pagedRows}
busyId={busyId}
onAdvance={advance}
onMove={setMoveItem}
@@ -287,6 +314,14 @@ export function InventoryWorkbench({ items, isLoading, onLastMile }: InventoryWo
allSelected={allSelected}
someSelected={someSelected}
/>
<RuleEngineListFooter
pagination={controls.pagination}
pageCount={controls.pageCount}
totalCount={controls.totalCount}
itemLabel="items"
onPaginationChange={controls.setPagination}
/>
</Stack>
<MoveInventoryModal opened={Boolean(moveItem)} onClose={() => setMoveItem(null)} item={moveItem} />