mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
chore: add dropdown and also updated tables to use dropdown instead
This commit is contained in:
@@ -2,8 +2,10 @@ import { useMemo, useState } from "react";
|
||||
import {
|
||||
Boxes,
|
||||
CheckCircle2,
|
||||
Eye,
|
||||
Filter,
|
||||
ListOrdered,
|
||||
MoreHorizontal,
|
||||
Pencil,
|
||||
Plus,
|
||||
Search,
|
||||
@@ -30,6 +32,11 @@ import {
|
||||
CardDescription,
|
||||
CardContent,
|
||||
Input,
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
} from "@edr/ui-common";
|
||||
|
||||
export default function DropdownSettingsPage() {
|
||||
@@ -121,12 +128,8 @@ export default function DropdownSettingsPage() {
|
||||
) : (
|
||||
<BehaviorChip label="Single" muted />
|
||||
)}
|
||||
{s.meta?.searchable ? (
|
||||
<BehaviorChip label="Searchable" />
|
||||
) : null}
|
||||
{s.meta?.clearable ? (
|
||||
<BehaviorChip label="Clearable" />
|
||||
) : null}
|
||||
{s.meta?.searchable ? <BehaviorChip label="Searchable" /> : null}
|
||||
{s.meta?.clearable ? <BehaviorChip label="Clearable" /> : null}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@@ -157,32 +160,53 @@ export default function DropdownSettingsPage() {
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: () => <div className="text-right">Actions</div>,
|
||||
size: 40,
|
||||
cell: ({ row }) => {
|
||||
const setting = row.original;
|
||||
return (
|
||||
<div className="flex justify-end gap-2">
|
||||
<ManageDropdownOptionsDialog setting={setting}>
|
||||
<Button variant="outline" size="sm">
|
||||
<CheckCircle2 />
|
||||
Options
|
||||
</Button>
|
||||
</ManageDropdownOptionsDialog>
|
||||
|
||||
<EditDropdownSettingDialog mode="edit" setting={setting}>
|
||||
<Button variant="outline" size="icon">
|
||||
<Pencil />
|
||||
</Button>
|
||||
</EditDropdownSettingDialog>
|
||||
|
||||
<DeleteDropdownSettingDialog
|
||||
settingLabel={setting.label}
|
||||
settingCode={setting.code}
|
||||
>
|
||||
<Button variant="outline" size="icon">
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</DeleteDropdownSettingDialog>
|
||||
<div
|
||||
className="flex justify-end"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<MoreHorizontal />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem>
|
||||
<Eye />
|
||||
View
|
||||
</DropdownMenuItem>
|
||||
<ManageDropdownOptionsDialog setting={setting}>
|
||||
<DropdownMenuItem onSelect={(e: Event) => e.preventDefault()}>
|
||||
<CheckCircle2 />
|
||||
Options
|
||||
</DropdownMenuItem>
|
||||
</ManageDropdownOptionsDialog>
|
||||
<DropdownMenuSeparator />
|
||||
<EditDropdownSettingDialog mode="edit" setting={setting}>
|
||||
<DropdownMenuItem onSelect={(e: Event) => e.preventDefault()}>
|
||||
<Pencil />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
</EditDropdownSettingDialog>
|
||||
<DropdownMenuSeparator />
|
||||
<DeleteDropdownSettingDialog
|
||||
settingLabel={setting.label}
|
||||
settingCode={setting.code}
|
||||
>
|
||||
<DropdownMenuItem
|
||||
onSelect={(e: Event) => e.preventDefault()}
|
||||
variant="destructive"
|
||||
>
|
||||
<Trash2 />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DeleteDropdownSettingDialog>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@@ -212,13 +236,16 @@ export default function DropdownSettingsPage() {
|
||||
|
||||
<div className="flex flex-col items-stretch gap-3 sm:flex-row sm:items-center">
|
||||
<div className="relative w-full sm:w-80">
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-slate-400" />
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" />
|
||||
<Input
|
||||
type="search"
|
||||
value={query}
|
||||
onChange={(e) => {
|
||||
setQuery(e.target.value);
|
||||
setPagination({ pageIndex: 0, pageSize: pagination.pageSize });
|
||||
setPagination({
|
||||
pageIndex: 0,
|
||||
pageSize: pagination.pageSize,
|
||||
});
|
||||
}}
|
||||
placeholder="Search by code, label, description..."
|
||||
className="pl-8!"
|
||||
@@ -312,7 +339,7 @@ export default function DropdownSettingsPage() {
|
||||
columns={columns}
|
||||
data={paginatedData}
|
||||
status="success"
|
||||
onRowClick={() => {}}
|
||||
onRowClick={() => { }}
|
||||
pagination={{
|
||||
pageIndex: pagination.pageIndex,
|
||||
pageSize: pagination.pageSize,
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
DollarSign,
|
||||
Download,
|
||||
Filter,
|
||||
MoreHorizontal,
|
||||
Pencil,
|
||||
Plus,
|
||||
Receipt,
|
||||
@@ -15,11 +16,7 @@ import {
|
||||
import Breadcrumbs from "@/components/Breadcrumbs";
|
||||
import NewInvoicePage from "./NewInvoicePage";
|
||||
import DeleteInvoiceDialog from "./DeleteInvoiceDialog";
|
||||
import {
|
||||
formatCurrency,
|
||||
invoices,
|
||||
type InvoiceStatus,
|
||||
} from "./invoices.mock";
|
||||
import { formatCurrency, invoices, type InvoiceStatus } from "./invoices.mock";
|
||||
import {
|
||||
DataTable,
|
||||
DataTableFooter,
|
||||
@@ -32,6 +29,11 @@ import {
|
||||
CardDescription,
|
||||
CardContent,
|
||||
Input,
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
} from "@edr/ui-common";
|
||||
|
||||
type FilterValue = "All" | InvoiceStatus;
|
||||
@@ -83,7 +85,9 @@ export default function BillingPage() {
|
||||
inv.currency === "USD",
|
||||
)
|
||||
.reduce((sum, inv) => sum + inv.amount, 0);
|
||||
const overdueCount = invoices.filter((inv) => inv.status === "Overdue").length;
|
||||
const overdueCount = invoices.filter(
|
||||
(inv) => inv.status === "Overdue",
|
||||
).length;
|
||||
|
||||
const columns: ColumnDef<(typeof invoices)[number]>[] = [
|
||||
{
|
||||
@@ -135,39 +139,56 @@ export default function BillingPage() {
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: () => <div className="text-right">Actions</div>,
|
||||
size: 40,
|
||||
cell: ({ row }) => {
|
||||
const invoice = row.original;
|
||||
return (
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline" size="icon" aria-label="Download invoice">
|
||||
<Download />
|
||||
</Button>
|
||||
|
||||
<NewInvoicePage
|
||||
mode="edit"
|
||||
invoice={{
|
||||
number: invoice.number,
|
||||
customerId: invoice.customerId,
|
||||
bookingReference: invoice.bookingReference,
|
||||
amount: invoice.amount,
|
||||
currency: invoice.currency,
|
||||
status: invoice.status,
|
||||
issueDate: invoice.issueDate,
|
||||
dueDate: invoice.dueDate,
|
||||
notes: invoice.notes,
|
||||
}}
|
||||
>
|
||||
<Button variant="outline" size="icon">
|
||||
<Pencil />
|
||||
</Button>
|
||||
</NewInvoicePage>
|
||||
|
||||
<DeleteInvoiceDialog invoiceNumber={invoice.number}>
|
||||
<Button variant="outline" size="icon">
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</DeleteInvoiceDialog>
|
||||
<div
|
||||
className="flex justify-end"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<MoreHorizontal />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem>
|
||||
<Download />
|
||||
Download
|
||||
</DropdownMenuItem>
|
||||
<NewInvoicePage
|
||||
mode="edit"
|
||||
invoice={{
|
||||
number: invoice.number,
|
||||
customerId: invoice.customerId,
|
||||
bookingReference: invoice.bookingReference,
|
||||
amount: invoice.amount,
|
||||
currency: invoice.currency,
|
||||
status: invoice.status,
|
||||
issueDate: invoice.issueDate,
|
||||
dueDate: invoice.dueDate,
|
||||
notes: invoice.notes,
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItem onSelect={(e: Event) => e.preventDefault()}>
|
||||
<Pencil />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
</NewInvoicePage>
|
||||
<DropdownMenuSeparator />
|
||||
<DeleteInvoiceDialog invoiceNumber={invoice.number}>
|
||||
<DropdownMenuItem
|
||||
onSelect={(e: Event) => e.preventDefault()}
|
||||
variant="destructive"
|
||||
>
|
||||
<Trash2 />
|
||||
Void
|
||||
</DropdownMenuItem>
|
||||
</DeleteInvoiceDialog>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@@ -191,13 +212,16 @@ export default function BillingPage() {
|
||||
|
||||
<div className="flex flex-col items-stretch gap-3 sm:flex-row sm:items-center">
|
||||
<div className="relative w-full sm:w-80">
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-slate-400" />
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" />
|
||||
<Input
|
||||
type="search"
|
||||
value={query}
|
||||
onChange={(e) => {
|
||||
setQuery(e.target.value);
|
||||
setPagination({ pageIndex: 0, pageSize: pagination.pageSize });
|
||||
setPagination({
|
||||
pageIndex: 0,
|
||||
pageSize: pagination.pageSize,
|
||||
});
|
||||
}}
|
||||
placeholder="Search invoices..."
|
||||
className="pl-8!"
|
||||
@@ -271,7 +295,10 @@ export default function BillingPage() {
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setFilter(f);
|
||||
setPagination({ pageIndex: 0, pageSize: pagination.pageSize });
|
||||
setPagination({
|
||||
pageIndex: 0,
|
||||
pageSize: pagination.pageSize,
|
||||
});
|
||||
}}
|
||||
className={
|
||||
isActive
|
||||
@@ -315,7 +342,7 @@ export default function BillingPage() {
|
||||
columns={columns}
|
||||
data={paginatedData}
|
||||
status="success"
|
||||
onRowClick={() => {}}
|
||||
onRowClick={() => { }}
|
||||
pagination={{
|
||||
pageIndex: pagination.pageIndex,
|
||||
pageSize: pagination.pageSize,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useMemo } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
ArrowRight,
|
||||
Clock,
|
||||
Eye,
|
||||
Filter,
|
||||
MoreHorizontal,
|
||||
Package,
|
||||
Pencil,
|
||||
Plus,
|
||||
@@ -29,9 +30,15 @@ import {
|
||||
CardDescription,
|
||||
CardContent,
|
||||
Input,
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
} from "@edr/ui-common";
|
||||
|
||||
export default function BookingsPage() {
|
||||
const navigate = useNavigate();
|
||||
const { pagination, setPagination } = usePagination({ pageSize: 10 });
|
||||
|
||||
const total = bookings.length;
|
||||
@@ -39,10 +46,7 @@ export default function BookingsPage() {
|
||||
const start = pagination.pageIndex * pagination.pageSize;
|
||||
const end = Math.min(start + pagination.pageSize, total);
|
||||
|
||||
const paginatedData = useMemo(
|
||||
() => bookings.slice(start, end),
|
||||
[start, end],
|
||||
);
|
||||
const paginatedData = useMemo(() => bookings.slice(start, end), [start, end]);
|
||||
|
||||
const columns: ColumnDef<(typeof bookings)[number]>[] = [
|
||||
{
|
||||
@@ -100,45 +104,62 @@ export default function BookingsPage() {
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: () => <div className="text-right">Actions</div>,
|
||||
size: 40,
|
||||
cell: ({ row }) => {
|
||||
const booking = row.original;
|
||||
return (
|
||||
<div className="flex justify-end gap-2">
|
||||
<Link to={`/bookings/${booking.id}`}>
|
||||
<Button variant="outline" size="icon">
|
||||
<Eye />
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<NewBookingPage
|
||||
mode="edit"
|
||||
booking={{
|
||||
customerId: booking.customerId,
|
||||
cargoType: booking.cargoType,
|
||||
originStation: booking.originStation,
|
||||
destinationStation: booking.destinationStation,
|
||||
transportMode: booking.transportMode,
|
||||
legs: booking.legs,
|
||||
containerType: booking.containerType,
|
||||
containerCount: booking.containerCount,
|
||||
weightTons: booking.weightTons,
|
||||
requestedDate: booking.requestedDate,
|
||||
priority: booking.priority,
|
||||
cargoDescription: booking.cargoDescription,
|
||||
specialInstructions: booking.specialInstructions,
|
||||
}}
|
||||
>
|
||||
<Button variant="outline" size="icon">
|
||||
<Pencil />
|
||||
</Button>
|
||||
</NewBookingPage>
|
||||
|
||||
<DeleteBookingDialog bookingReference={booking.reference}>
|
||||
<Button variant="outline" size="icon">
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</DeleteBookingDialog>
|
||||
<div
|
||||
className="flex justify-end"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<MoreHorizontal />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
onClick={() => navigate(`/bookings/${booking.id}`)}
|
||||
>
|
||||
<Eye />
|
||||
View
|
||||
</DropdownMenuItem>
|
||||
<NewBookingPage
|
||||
mode="edit"
|
||||
booking={{
|
||||
customerId: booking.customerId,
|
||||
cargoType: booking.cargoType,
|
||||
originStation: booking.originStation,
|
||||
destinationStation: booking.destinationStation,
|
||||
transportMode: booking.transportMode,
|
||||
legs: booking.legs,
|
||||
containerType: booking.containerType,
|
||||
containerCount: booking.containerCount,
|
||||
weightTons: booking.weightTons,
|
||||
requestedDate: booking.requestedDate,
|
||||
priority: booking.priority,
|
||||
cargoDescription: booking.cargoDescription,
|
||||
specialInstructions: booking.specialInstructions,
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItem onSelect={(e: Event) => e.preventDefault()}>
|
||||
<Pencil />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
</NewBookingPage>
|
||||
<DropdownMenuSeparator />
|
||||
<DeleteBookingDialog bookingReference={booking.reference}>
|
||||
<DropdownMenuItem
|
||||
onSelect={(e: Event) => e.preventDefault()}
|
||||
variant="destructive"
|
||||
>
|
||||
<Trash2 />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DeleteBookingDialog>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@@ -162,7 +183,7 @@ export default function BookingsPage() {
|
||||
|
||||
<div className="flex flex-col items-stretch gap-3 sm:flex-row sm:items-center">
|
||||
<div className="relative w-full sm:w-80">
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-slate-400" />
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" />
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Search bookings..."
|
||||
@@ -243,7 +264,9 @@ export default function BookingsPage() {
|
||||
columns={columns}
|
||||
data={paginatedData}
|
||||
status="success"
|
||||
onRowClick={() => {}}
|
||||
onRowClick={(row) =>
|
||||
navigate(`/bookings/${(row as (typeof bookings)[number]).id}`)
|
||||
}
|
||||
pagination={{
|
||||
pageIndex: pagination.pageIndex,
|
||||
pageSize: pagination.pageSize,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
AlertTriangle,
|
||||
ArrowRight,
|
||||
CheckCircle2,
|
||||
Eye,
|
||||
Filter,
|
||||
MoreHorizontal,
|
||||
Package,
|
||||
Pencil,
|
||||
Plus,
|
||||
@@ -17,10 +18,7 @@ import {
|
||||
import Breadcrumbs from "@/components/Breadcrumbs";
|
||||
import NewConsignmentPage from "./NewConsignmentPage";
|
||||
import DeleteConsignmentDialog from "./DeleteConsignmentDialog";
|
||||
import {
|
||||
consignments,
|
||||
type ConsignmentStatus,
|
||||
} from "./consignments.mock";
|
||||
import { consignments, type ConsignmentStatus } from "./consignments.mock";
|
||||
import {
|
||||
DataTable,
|
||||
DataTableFooter,
|
||||
@@ -33,6 +31,11 @@ import {
|
||||
CardDescription,
|
||||
CardContent,
|
||||
Input,
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
} from "@edr/ui-common";
|
||||
|
||||
type FilterValue = "All" | ConsignmentStatus;
|
||||
@@ -47,6 +50,7 @@ const FILTERS: FilterValue[] = [
|
||||
];
|
||||
|
||||
export default function ConsignmentsPage() {
|
||||
const navigate = useNavigate();
|
||||
const { pagination, setPagination } = usePagination({ pageSize: 10 });
|
||||
const [filter, setFilter] = useState<FilterValue>("All");
|
||||
const [query, setQuery] = useState("");
|
||||
@@ -143,7 +147,8 @@ export default function ConsignmentsPage() {
|
||||
<div className="text-sm text-slate-700">
|
||||
<p>{c.cargoType}</p>
|
||||
<p className="text-xs text-slate-500">
|
||||
{c.pieces} pcs · {c.weightKg.toLocaleString()} kg · {c.volumeM3} m³
|
||||
{c.pieces} pcs · {c.weightKg.toLocaleString()} kg · {c.volumeM3}{" "}
|
||||
m³
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
@@ -156,46 +161,63 @@ export default function ConsignmentsPage() {
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: () => <div className="text-right">Actions</div>,
|
||||
size: 40,
|
||||
cell: ({ row }) => {
|
||||
const consignment = row.original;
|
||||
return (
|
||||
<div className="flex justify-end gap-2">
|
||||
<Link to={`/consignments/${consignment.id}`}>
|
||||
<Button variant="outline" size="icon">
|
||||
<Eye />
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<NewConsignmentPage
|
||||
mode="edit"
|
||||
consignment={{
|
||||
trackingNumber: consignment.trackingNumber,
|
||||
bookingId: consignment.bookingId,
|
||||
bookingReference: consignment.bookingReference,
|
||||
cargoType: consignment.cargoType,
|
||||
description: consignment.description,
|
||||
weightKg: consignment.weightKg,
|
||||
volumeM3: consignment.volumeM3,
|
||||
pieces: consignment.pieces,
|
||||
hazardous: consignment.hazardous,
|
||||
specialHandling: consignment.specialHandling,
|
||||
status: consignment.status,
|
||||
estimatedDelivery: consignment.estimatedDelivery,
|
||||
}}
|
||||
>
|
||||
<Button variant="outline" size="icon">
|
||||
<Pencil />
|
||||
</Button>
|
||||
</NewConsignmentPage>
|
||||
|
||||
<DeleteConsignmentDialog
|
||||
trackingNumber={consignment.trackingNumber}
|
||||
>
|
||||
<Button variant="outline" size="icon">
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</DeleteConsignmentDialog>
|
||||
<div
|
||||
className="flex justify-end"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<MoreHorizontal />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
onClick={() => navigate(`/consignments/${consignment.id}`)}
|
||||
>
|
||||
<Eye />
|
||||
View
|
||||
</DropdownMenuItem>
|
||||
<NewConsignmentPage
|
||||
mode="edit"
|
||||
consignment={{
|
||||
trackingNumber: consignment.trackingNumber,
|
||||
bookingId: consignment.bookingId,
|
||||
bookingReference: consignment.bookingReference,
|
||||
cargoType: consignment.cargoType,
|
||||
description: consignment.description,
|
||||
weightKg: consignment.weightKg,
|
||||
volumeM3: consignment.volumeM3,
|
||||
pieces: consignment.pieces,
|
||||
hazardous: consignment.hazardous,
|
||||
specialHandling: consignment.specialHandling,
|
||||
status: consignment.status,
|
||||
estimatedDelivery: consignment.estimatedDelivery,
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItem onSelect={(e: Event) => e.preventDefault()}>
|
||||
<Pencil />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
</NewConsignmentPage>
|
||||
<DropdownMenuSeparator />
|
||||
<DeleteConsignmentDialog
|
||||
trackingNumber={consignment.trackingNumber}
|
||||
>
|
||||
<DropdownMenuItem
|
||||
onSelect={(e: Event) => e.preventDefault()}
|
||||
variant="destructive"
|
||||
>
|
||||
<Trash2 />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DeleteConsignmentDialog>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@@ -219,13 +241,16 @@ export default function ConsignmentsPage() {
|
||||
|
||||
<div className="flex flex-col items-stretch gap-3 sm:flex-row sm:items-center">
|
||||
<div className="relative w-full sm:w-80">
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-slate-400" />
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" />
|
||||
<Input
|
||||
type="search"
|
||||
value={query}
|
||||
onChange={(e) => {
|
||||
setQuery(e.target.value);
|
||||
setPagination({ pageIndex: 0, pageSize: pagination.pageSize });
|
||||
setPagination({
|
||||
pageIndex: 0,
|
||||
pageSize: pagination.pageSize,
|
||||
});
|
||||
}}
|
||||
placeholder="Search consignments..."
|
||||
className="pl-8!"
|
||||
@@ -313,7 +338,10 @@ export default function ConsignmentsPage() {
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setFilter(f);
|
||||
setPagination({ pageIndex: 0, pageSize: pagination.pageSize });
|
||||
setPagination({
|
||||
pageIndex: 0,
|
||||
pageSize: pagination.pageSize,
|
||||
});
|
||||
}}
|
||||
className={
|
||||
isActive
|
||||
@@ -357,7 +385,11 @@ export default function ConsignmentsPage() {
|
||||
columns={columns}
|
||||
data={paginatedData}
|
||||
status="success"
|
||||
onRowClick={() => {}}
|
||||
onRowClick={(row) =>
|
||||
navigate(
|
||||
`/consignments/${(row as (typeof consignments)[number]).id}`,
|
||||
)
|
||||
}
|
||||
pagination={{
|
||||
pageIndex: pagination.pageIndex,
|
||||
pageSize: pagination.pageSize,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useMemo } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
Clock3,
|
||||
Eye,
|
||||
Filter,
|
||||
MoreHorizontal,
|
||||
Pencil,
|
||||
Plus,
|
||||
Search,
|
||||
@@ -33,9 +34,15 @@ import {
|
||||
CardDescription,
|
||||
CardContent,
|
||||
Input,
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
} from "@edr/ui-common";
|
||||
|
||||
export default function CustomerPage() {
|
||||
const navigate = useNavigate();
|
||||
const { pagination, setPagination } = usePagination({ pageSize: 10 });
|
||||
|
||||
const total = customers.length;
|
||||
@@ -85,42 +92,59 @@ export default function CustomerPage() {
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: () => <div className="text-right">Actions</div>,
|
||||
size: 40,
|
||||
cell: ({ row }) => {
|
||||
const customer = row.original;
|
||||
return (
|
||||
<div className="flex justify-end gap-2">
|
||||
<Link to={`/customers/${customer.id}`}>
|
||||
<Button size="icon" variant="outline">
|
||||
<Eye />
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<NewCustomerPage
|
||||
mode="edit"
|
||||
customer={{
|
||||
companyName: customer.company,
|
||||
customerType: customer.customerType,
|
||||
contactPerson: customer.name,
|
||||
email: customer.email,
|
||||
phone: customer.phone,
|
||||
tinNumber: customer.tinNumber,
|
||||
city: customer.city,
|
||||
country: customer.country,
|
||||
address: customer.address,
|
||||
notes: customer.notes,
|
||||
}}
|
||||
>
|
||||
<Button size="icon" variant="outline">
|
||||
<Pencil />
|
||||
</Button>
|
||||
</NewCustomerPage>
|
||||
|
||||
<DeleteCustomerDialog customerName={customer.name}>
|
||||
<Button size="icon" type="button" variant="destructive">
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</DeleteCustomerDialog>
|
||||
<div
|
||||
className="flex justify-end"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<MoreHorizontal />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
onClick={() => navigate(`/customers/${customer.id}`)}
|
||||
>
|
||||
<Eye />
|
||||
View
|
||||
</DropdownMenuItem>
|
||||
<NewCustomerPage
|
||||
mode="edit"
|
||||
customer={{
|
||||
companyName: customer.company,
|
||||
customerType: customer.customerType,
|
||||
contactPerson: customer.name,
|
||||
email: customer.email,
|
||||
phone: customer.phone,
|
||||
tinNumber: customer.tinNumber,
|
||||
city: customer.city,
|
||||
country: customer.country,
|
||||
address: customer.address,
|
||||
notes: customer.notes,
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItem onSelect={(e: Event) => e.preventDefault()}>
|
||||
<Pencil />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
</NewCustomerPage>
|
||||
<DropdownMenuSeparator />
|
||||
<DeleteCustomerDialog customerName={customer.name}>
|
||||
<DropdownMenuItem
|
||||
onSelect={(e: Event) => e.preventDefault()}
|
||||
variant="destructive"
|
||||
>
|
||||
<Trash2 />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DeleteCustomerDialog>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@@ -201,7 +225,7 @@ export default function CustomerPage() {
|
||||
columns={columns}
|
||||
data={paginatedData}
|
||||
status="success"
|
||||
onRowClick={() => { }}
|
||||
onRowClick={(row) => navigate(`/customers/${row.id}`)}
|
||||
pagination={{
|
||||
pageIndex: pagination.pageIndex,
|
||||
pageSize: pagination.pageSize,
|
||||
@@ -232,7 +256,7 @@ function StatCard({
|
||||
icon: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Card className="transition hover:shadow-md hover:ring-1 hover:ring-[#33578D]/20">
|
||||
<Card>
|
||||
<CardContent className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-slate-500">{title}</p>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
HardDrive,
|
||||
LayoutGrid,
|
||||
List,
|
||||
MoreHorizontal,
|
||||
Pencil,
|
||||
Plus,
|
||||
Search,
|
||||
@@ -40,6 +41,11 @@ import {
|
||||
CardDescription,
|
||||
CardContent,
|
||||
Input,
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
} from "@edr/ui-common";
|
||||
|
||||
type FilterValue = "All" | DocumentStatus;
|
||||
@@ -145,37 +151,58 @@ export default function DocumentsPage() {
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: () => <div className="text-right">Actions</div>,
|
||||
size: 40,
|
||||
cell: ({ row }) => {
|
||||
const doc = row.original;
|
||||
return (
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline" size="icon" aria-label="Preview">
|
||||
<Eye />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" aria-label="Download">
|
||||
<Download />
|
||||
</Button>
|
||||
<NewDocumentPage
|
||||
mode="edit"
|
||||
document={{
|
||||
name: doc.name,
|
||||
type: doc.type,
|
||||
status: doc.status,
|
||||
linkedType: doc.linkedType,
|
||||
linkedReference: doc.linkedReference,
|
||||
notes: doc.notes,
|
||||
}}
|
||||
>
|
||||
<Button variant="outline" size="icon">
|
||||
<Pencil />
|
||||
</Button>
|
||||
</NewDocumentPage>
|
||||
<DeleteDocumentDialog documentName={doc.name}>
|
||||
<Button variant="outline" size="icon">
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</DeleteDocumentDialog>
|
||||
<div
|
||||
className="flex justify-end"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<MoreHorizontal />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem>
|
||||
<Eye />
|
||||
Preview
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Download />
|
||||
Download
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<NewDocumentPage
|
||||
mode="edit"
|
||||
document={{
|
||||
name: doc.name,
|
||||
type: doc.type,
|
||||
status: doc.status,
|
||||
linkedType: doc.linkedType,
|
||||
linkedReference: doc.linkedReference,
|
||||
notes: doc.notes,
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItem onSelect={(e: Event) => e.preventDefault()}>
|
||||
<Pencil />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
</NewDocumentPage>
|
||||
<DropdownMenuSeparator />
|
||||
<DeleteDocumentDialog documentName={doc.name}>
|
||||
<DropdownMenuItem
|
||||
onSelect={(e: Event) => e.preventDefault()}
|
||||
variant="destructive"
|
||||
>
|
||||
<Trash2 />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DeleteDocumentDialog>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@@ -200,7 +227,7 @@ export default function DocumentsPage() {
|
||||
|
||||
<div className="flex flex-col items-stretch gap-3 sm:flex-row sm:items-center">
|
||||
<div className="relative w-full sm:w-80">
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-slate-400" />
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" />
|
||||
<Input
|
||||
type="search"
|
||||
value={query}
|
||||
@@ -466,33 +493,55 @@ function DocumentCard({ doc }: { doc: DocumentRecord }) {
|
||||
|
||||
<p className="text-xs text-slate-500">By {doc.uploadedBy}</p>
|
||||
|
||||
<div className="flex items-center justify-end gap-2 border-t pt-3">
|
||||
<Button variant="outline" size="icon" aria-label="Preview">
|
||||
<Eye />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" aria-label="Download">
|
||||
<Download />
|
||||
</Button>
|
||||
<NewDocumentPage
|
||||
mode="edit"
|
||||
document={{
|
||||
name: doc.name,
|
||||
type: doc.type,
|
||||
status: doc.status,
|
||||
linkedType: doc.linkedType,
|
||||
linkedReference: doc.linkedReference,
|
||||
notes: doc.notes,
|
||||
}}
|
||||
>
|
||||
<Button variant="outline" size="icon">
|
||||
<Pencil />
|
||||
</Button>
|
||||
</NewDocumentPage>
|
||||
<DeleteDocumentDialog documentName={doc.name}>
|
||||
<Button variant="outline" size="icon">
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</DeleteDocumentDialog>
|
||||
<div
|
||||
className="flex items-center justify-end gap-2 border-t pt-3"
|
||||
onClick={(e: React.MouseEvent) => e.stopPropagation()}
|
||||
>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="sm">
|
||||
<MoreHorizontal />
|
||||
Actions
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem>
|
||||
<Eye />
|
||||
Preview
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Download />
|
||||
Download
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<NewDocumentPage
|
||||
mode="edit"
|
||||
document={{
|
||||
name: doc.name,
|
||||
type: doc.type,
|
||||
status: doc.status,
|
||||
linkedType: doc.linkedType,
|
||||
linkedReference: doc.linkedReference,
|
||||
notes: doc.notes,
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItem onSelect={(e: Event) => e.preventDefault()}>
|
||||
<Pencil />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
</NewDocumentPage>
|
||||
<DropdownMenuSeparator />
|
||||
<DeleteDocumentDialog documentName={doc.name}>
|
||||
<DropdownMenuItem
|
||||
onSelect={(e: Event) => e.preventDefault()}
|
||||
variant="destructive"
|
||||
>
|
||||
<Trash2 />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DeleteDocumentDialog>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -3,9 +3,11 @@ import {
|
||||
ArrowRight,
|
||||
Building2,
|
||||
Clock,
|
||||
Eye,
|
||||
LayoutGrid,
|
||||
List,
|
||||
MapPin,
|
||||
MoreHorizontal,
|
||||
Pencil,
|
||||
Plus,
|
||||
Search,
|
||||
@@ -31,6 +33,11 @@ import {
|
||||
CardDescription,
|
||||
CardContent,
|
||||
Input,
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
} from "@edr/ui-common";
|
||||
|
||||
type FilterValue = "All" | ShipmentStatus;
|
||||
@@ -77,7 +84,7 @@ export default function TrackingPage() {
|
||||
|
||||
<div className="flex flex-col items-stretch gap-3 sm:flex-row sm:items-center">
|
||||
<div className="relative w-full sm:w-80">
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-slate-400" />
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" />
|
||||
<Input
|
||||
type="search"
|
||||
value={query}
|
||||
@@ -272,32 +279,46 @@ function ShipmentCard({ shipment }: { shipment: Shipment }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-2 border-t pt-3">
|
||||
<NewShipmentPage
|
||||
mode="edit"
|
||||
shipment={{
|
||||
bookingId: shipment.bookingId,
|
||||
bookingReference: shipment.bookingReference,
|
||||
originStation: shipment.originStation,
|
||||
destinationStation: shipment.destinationStation,
|
||||
mode: shipment.mode,
|
||||
status: shipment.status,
|
||||
currentLocation: shipment.currentLocation,
|
||||
eta: shipment.eta,
|
||||
}}
|
||||
>
|
||||
<Button variant="outline" size="sm">
|
||||
<Pencil />
|
||||
Edit
|
||||
</Button>
|
||||
</NewShipmentPage>
|
||||
|
||||
<DeleteShipmentDialog shipmentReference={shipment.reference}>
|
||||
<Button variant="outline" size="sm">
|
||||
<Trash2 />
|
||||
Remove
|
||||
</Button>
|
||||
</DeleteShipmentDialog>
|
||||
<div className="flex items-center justify-end gap-2 border-t pt-3" onClick={(e: React.MouseEvent) => e.stopPropagation()}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="sm">
|
||||
<MoreHorizontal />
|
||||
Actions
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem>
|
||||
<Eye />
|
||||
View
|
||||
</DropdownMenuItem>
|
||||
<NewShipmentPage
|
||||
mode="edit"
|
||||
shipment={{
|
||||
bookingId: shipment.bookingId,
|
||||
bookingReference: shipment.bookingReference,
|
||||
originStation: shipment.originStation,
|
||||
destinationStation: shipment.destinationStation,
|
||||
mode: shipment.mode,
|
||||
status: shipment.status,
|
||||
currentLocation: shipment.currentLocation,
|
||||
eta: shipment.eta,
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItem onSelect={(e: Event) => e.preventDefault()}>
|
||||
<Pencil />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
</NewShipmentPage>
|
||||
<DropdownMenuSeparator />
|
||||
<DeleteShipmentDialog shipmentReference={shipment.reference}>
|
||||
<DropdownMenuItem onSelect={(e: Event) => e.preventDefault()} variant="destructive">
|
||||
<Trash2 />
|
||||
Remove
|
||||
</DropdownMenuItem>
|
||||
</DeleteShipmentDialog>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -402,30 +423,45 @@ function ShipmentTable({ shipments: rows }: { shipments: Shipment[] }) {
|
||||
</td>
|
||||
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex justify-end gap-2">
|
||||
<NewShipmentPage
|
||||
mode="edit"
|
||||
shipment={{
|
||||
bookingId: shipment.bookingId,
|
||||
bookingReference: shipment.bookingReference,
|
||||
originStation: shipment.originStation,
|
||||
destinationStation: shipment.destinationStation,
|
||||
mode: shipment.mode,
|
||||
status: shipment.status,
|
||||
currentLocation: shipment.currentLocation,
|
||||
eta: shipment.eta,
|
||||
}}
|
||||
>
|
||||
<Button variant="outline" size="icon">
|
||||
<Pencil />
|
||||
</Button>
|
||||
</NewShipmentPage>
|
||||
|
||||
<DeleteShipmentDialog shipmentReference={shipment.reference}>
|
||||
<Button variant="outline" size="icon">
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</DeleteShipmentDialog>
|
||||
<div onClick={(e: React.MouseEvent) => e.stopPropagation()}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<MoreHorizontal />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem>
|
||||
<Eye />
|
||||
View
|
||||
</DropdownMenuItem>
|
||||
<NewShipmentPage
|
||||
mode="edit"
|
||||
shipment={{
|
||||
bookingId: shipment.bookingId,
|
||||
bookingReference: shipment.bookingReference,
|
||||
originStation: shipment.originStation,
|
||||
destinationStation: shipment.destinationStation,
|
||||
mode: shipment.mode,
|
||||
status: shipment.status,
|
||||
currentLocation: shipment.currentLocation,
|
||||
eta: shipment.eta,
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItem onSelect={(e: Event) => e.preventDefault()}>
|
||||
<Pencil />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
</NewShipmentPage>
|
||||
<DropdownMenuSeparator />
|
||||
<DeleteShipmentDialog shipmentReference={shipment.reference}>
|
||||
<DropdownMenuItem onSelect={(e: Event) => e.preventDefault()} variant="destructive">
|
||||
<Trash2 />
|
||||
Remove
|
||||
</DropdownMenuItem>
|
||||
</DeleteShipmentDialog>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import {
|
||||
Eye,
|
||||
Filter,
|
||||
Gauge,
|
||||
MoreHorizontal,
|
||||
Pencil,
|
||||
Plus,
|
||||
Search,
|
||||
@@ -26,6 +28,11 @@ import {
|
||||
CardDescription,
|
||||
CardContent,
|
||||
Input,
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
} from "@edr/ui-common";
|
||||
|
||||
type FilterValue = "All" | TrainStatus;
|
||||
@@ -120,38 +127,59 @@ export default function TrainsPage() {
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: () => <div className="text-right">Actions</div>,
|
||||
size: 40,
|
||||
cell: ({ row }) => {
|
||||
const train = row.original;
|
||||
return (
|
||||
<div className="flex justify-end gap-2">
|
||||
<NewTrainPage
|
||||
mode="edit"
|
||||
train={{
|
||||
code: train.code,
|
||||
name: train.name,
|
||||
type: train.type,
|
||||
status: train.status,
|
||||
capacityTons: train.capacityTons,
|
||||
depot: train.depot,
|
||||
manufacturer: train.manufacturer,
|
||||
mileageKm: train.mileageKm,
|
||||
lastMaintenance: train.lastMaintenance,
|
||||
nextMaintenance: train.nextMaintenance,
|
||||
currentAssignment: train.currentAssignment,
|
||||
yearBuilt: train.yearBuilt,
|
||||
}}
|
||||
>
|
||||
<Button variant="outline" size="icon">
|
||||
<Pencil />
|
||||
</Button>
|
||||
</NewTrainPage>
|
||||
|
||||
<DeleteTrainDialog trainCode={train.code}>
|
||||
<Button variant="outline" size="icon">
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</DeleteTrainDialog>
|
||||
<div
|
||||
className="flex justify-end"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<MoreHorizontal />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem>
|
||||
<Eye />
|
||||
View
|
||||
</DropdownMenuItem>
|
||||
<NewTrainPage
|
||||
mode="edit"
|
||||
train={{
|
||||
code: train.code,
|
||||
name: train.name,
|
||||
type: train.type,
|
||||
status: train.status,
|
||||
capacityTons: train.capacityTons,
|
||||
depot: train.depot,
|
||||
manufacturer: train.manufacturer,
|
||||
mileageKm: train.mileageKm,
|
||||
lastMaintenance: train.lastMaintenance,
|
||||
nextMaintenance: train.nextMaintenance,
|
||||
currentAssignment: train.currentAssignment,
|
||||
yearBuilt: train.yearBuilt,
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItem onSelect={(e: Event) => e.preventDefault()}>
|
||||
<Pencil />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
</NewTrainPage>
|
||||
<DropdownMenuSeparator />
|
||||
<DeleteTrainDialog trainCode={train.code}>
|
||||
<DropdownMenuItem
|
||||
onSelect={(e: Event) => e.preventDefault()}
|
||||
variant="destructive"
|
||||
>
|
||||
<Trash2 />
|
||||
Retire
|
||||
</DropdownMenuItem>
|
||||
</DeleteTrainDialog>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@@ -175,13 +203,16 @@ export default function TrainsPage() {
|
||||
|
||||
<div className="flex flex-col items-stretch gap-3 sm:flex-row sm:items-center">
|
||||
<div className="relative w-full sm:w-80">
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-slate-400" />
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" />
|
||||
<Input
|
||||
type="search"
|
||||
value={query}
|
||||
onChange={(e) => {
|
||||
setQuery(e.target.value);
|
||||
setPagination({ pageIndex: 0, pageSize: pagination.pageSize });
|
||||
setPagination({
|
||||
pageIndex: 0,
|
||||
pageSize: pagination.pageSize,
|
||||
});
|
||||
}}
|
||||
placeholder="Search trains..."
|
||||
className="pl-8!"
|
||||
@@ -255,7 +286,10 @@ export default function TrainsPage() {
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setFilter(f);
|
||||
setPagination({ pageIndex: 0, pageSize: pagination.pageSize });
|
||||
setPagination({
|
||||
pageIndex: 0,
|
||||
pageSize: pagination.pageSize,
|
||||
});
|
||||
}}
|
||||
className={
|
||||
isActive
|
||||
@@ -299,7 +333,7 @@ export default function TrainsPage() {
|
||||
columns={columns}
|
||||
data={paginatedData}
|
||||
status="success"
|
||||
onRowClick={() => {}}
|
||||
onRowClick={() => { }}
|
||||
pagination={{
|
||||
pageIndex: pagination.pageIndex,
|
||||
pageSize: pagination.pageSize,
|
||||
|
||||
255
packages/ui-common/src/components/dropdown-menu.tsx
Normal file
255
packages/ui-common/src/components/dropdown-menu.tsx
Normal file
@@ -0,0 +1,255 @@
|
||||
import * as React from "react"
|
||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
|
||||
|
||||
import { cn } from "../lib/utils"
|
||||
|
||||
function DropdownMenu({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
||||
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
||||
}
|
||||
|
||||
function DropdownMenuPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Trigger
|
||||
data-slot="dropdown-menu-trigger"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuContent({
|
||||
className,
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
data-slot="dropdown-menu-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuItem({
|
||||
className,
|
||||
inset,
|
||||
variant = "default",
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
variant?: "default" | "destructive"
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Item
|
||||
data-slot="dropdown-menu-item"
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuCheckboxItem({
|
||||
className,
|
||||
children,
|
||||
checked,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
data-slot="dropdown-menu-checkbox-item"
|
||||
className={cn(
|
||||
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CheckIcon className="size-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuRadioGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.RadioGroup
|
||||
data-slot="dropdown-menu-radio-group"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuRadioItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
data-slot="dropdown-menu-radio-item"
|
||||
className={cn(
|
||||
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CircleIcon className="size-2 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuLabel({
|
||||
className,
|
||||
inset,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Label
|
||||
data-slot="dropdown-menu-label"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
data-slot="dropdown-menu-separator"
|
||||
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuShortcut({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
data-slot="dropdown-menu-shortcut"
|
||||
className={cn(
|
||||
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuSub({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
||||
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
|
||||
}
|
||||
|
||||
function DropdownMenuSubTrigger({
|
||||
className,
|
||||
inset,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
data-slot="dropdown-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto size-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuSubContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuSubContent,
|
||||
}
|
||||
@@ -23,6 +23,6 @@ export * from "./components/input";
|
||||
export * from "./components/textarea";
|
||||
export * from "./components/label";
|
||||
export * from "./components/card";
|
||||
|
||||
export * from "./components/dropdown-menu";
|
||||
export * from "./components/data-table";
|
||||
export * from "./components/dialog";
|
||||
|
||||
Reference in New Issue
Block a user