chore: add dropdown and also updated tables to use dropdown instead

This commit is contained in:
ghost2023
2026-05-20 13:49:17 +03:00
parent 76dcc48a38
commit 0440206c06
10 changed files with 846 additions and 339 deletions

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,

View 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,
}

View File

@@ -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";