updates on the ui changes

This commit is contained in:
2026-09-19 15:50:02 +05:30
parent 3e2e9cad3a
commit 7992208361
18 changed files with 243 additions and 263 deletions

View File

@@ -150,14 +150,6 @@ export default function AppUsers() {
return (
<Stack space="lg">
<PageHeader
actions={
<Button onClick={openCreate}>
<Plus className="mr-1.5 h-4 w-4" /> New user
</Button>
}
/>
<Grid cols={3}>
<KpiCard
icon={Users}
@@ -194,6 +186,11 @@ export default function AppUsers() {
onSearchChange={setSearch}
placeholder="Search by name, email, phone or role"
count={{ shown: rows.length, total: users.length, noun: 'users' }}
actions={
<Button onClick={openCreate}>
<Plus className="mr-1.5 h-4 w-4" /> New user
</Button>
}
/>
<DataTable

View File

@@ -421,14 +421,6 @@ export default function Bookings() {
return (
<Stack space="lg">
<PageHeader
actions={
<Button variant="outline" onClick={() => refetch()} loading={isFetching}>
<RefreshCw className="mr-1.5 h-4 w-4" /> Refresh
</Button>
}
/>
{isError && <Alert tone="danger">{messageOf(error, 'Could not load bookings')}</Alert>}
{scan?.truncated && (
@@ -466,6 +458,11 @@ export default function Bookings() {
placeholder="Search booking no, address or status"
onExport={() => exportRows(rows, EXPORT_COLUMNS, `customer-bookings-${tab}`)}
exportDisabled={rows.length === 0}
actions={
<Button variant="outline" onClick={() => refetch()} loading={isFetching}>
<RefreshCw className="mr-1.5 h-4 w-4" /> Refresh
</Button>
}
/>
</div>
</div>

View File

@@ -405,14 +405,6 @@ export default function Tenants() {
return (
<Stack space="lg">
<PageHeader
actions={
<Button onClick={() => navigate('/doormile/clients/create')}>
<Plus className="mr-1.5 h-4 w-4" /> New client
</Button>
}
/>
<Grid cols={4}>
<KpiCard icon={Building2} value={stats.total} label="Total clients" tone="brand" index={0} loading={isLoading} />
<KpiCard
@@ -464,6 +456,9 @@ export default function Tenants() {
placeholder="Search by client name, email or phone"
className="w-full sm:w-72"
/>
<Button onClick={() => navigate('/doormile/clients/create')}>
<Plus className="mr-1.5 h-4 w-4" /> New client
</Button>
</div>
</div>

View File

@@ -180,16 +180,13 @@ export default function CompetitiveIntel() {
return (
<Stack space="lg">
<PageHeader
actions={
<Stack space="sm">
<div className="flex flex-wrap items-center justify-between gap-3 w-full">
<Tabs tabs={TABS} value={tab} onChange={changeTab} />
<Button onClick={openCreate}>
<Plus className="mr-1.5 h-4 w-4" /> {onBranches ? 'New branch' : 'New rate'}
</Button>
}
/>
<Stack space="sm">
<Tabs tabs={TABS} value={tab} onChange={changeTab} />
</div>
<div className="flex items-center justify-between">
<p className="text-caption text-ink-3">

View File

@@ -141,16 +141,6 @@ export default function Customers({ embedded = false }) {
return (
<Stack space="lg">
{!embedded && (
<PageHeader
actions={
<Button onClick={() => navigate('/doormile/customer/create')}>
<Plus className="mr-1.5 h-4 w-4" /> New customer
</Button>
}
/>
)}
<Grid cols={4}>
<KpiCard
icon={Users}
@@ -195,6 +185,11 @@ export default function Customers({ embedded = false }) {
onSearchChange={setSearch}
placeholder="Search by name, phone or email"
count={{ shown: rows.length, total: customers.length, noun: 'customers' }}
actions={
<Button onClick={() => navigate('/doormile/customer/create')}>
<Plus className="mr-1.5 h-4 w-4" /> New customer
</Button>
}
/>
<DataTable

View File

@@ -1,12 +1,11 @@
import React, { useEffect, useMemo, useState } from 'react';
import dayjs from 'dayjs';
import { Ban, Bell, Bike, FileSpreadsheet, PackageSearch, Truck } from 'lucide-react';
import { Ban, Bell, Bike, Clock, FileSpreadsheet, PackageSearch, Truck } from 'lucide-react';
import {
Alert, Button, DataTable, DatePicker, Drawer, EmptyState, Field, IconButton,
PageHeader, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Modal,
PageHeader, SearchInput, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Modal,
Stack, StatusBadge, Surface, Tabs, Textarea, ZoneSelector,
} from '@/components/ds';
import { ListToolbar } from '@/components/doormile/ListToolbar';
import {
useBooking, useCancelDelivery, useChangeDeliveryRider, useDeliveries,
useDeliveryCounts, useNotifyMiler, useRiderOptions, useTenants,
@@ -390,30 +389,24 @@ export default function Deliveries() {
return (
<Stack space="lg">
<PageHeader
actions={
<DatePicker
value={selectedDate}
onChange={(d) => {
if (d) {
const s = dayjs(d).format('YYYY-MM-DD');
setDateParams({ from: s, to: s });
setSelectedDate(s);
}
}}
/>
}
/>
<Stack space="sm">
<Tabs tabs={tabs} value={tab} onChange={setTab} />
{/* Row 1: Tabs on left, DatePicker & Export on right */}
<div className="flex flex-wrap items-center justify-between gap-3 w-full">
<div className="no-scrollbar -mx-1 max-w-full min-w-0 overflow-x-auto px-1 py-0.5">
<Tabs tabs={tabs} value={tab} onChange={setTab} className="shrink-0" />
</div>
<ListToolbar
search={search}
onSearchChange={setSearch}
placeholder="Search by order, client, miler, customer or address"
count={{ shown: rows.length, total: batchRows.length, noun: 'deliveries' }}
actions={
<div className="flex items-center gap-2 shrink-0">
<DatePicker
value={selectedDate}
onChange={(d) => {
if (d) {
const s = dayjs(d).format('YYYY-MM-DD');
setDateParams({ from: s, to: s });
setSelectedDate(s);
}
}}
/>
<Button
variant="ghost"
onClick={() => exportRows(rows, exportColumns, `deliveries-${tab}-${dateParams.from || 'all'}`)}
@@ -421,50 +414,97 @@ export default function Deliveries() {
>
<FileSpreadsheet className="mr-1.5 h-4 w-4" /> Export
</Button>
}
>
<ZoneSelector className="w-full sm:w-auto" />
<Select value={batch} onValueChange={setBatch}>
<SelectTrigger className="w-full sm:w-52">
<SelectValue />
</SelectTrigger>
<SelectContent>
{BATCH_OPTIONS.map((option) => (
<SelectItem key={option.id} value={option.id}>
{option.label} · {option.range}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<Select value={tenantId} onValueChange={setTenantId}>
<SelectTrigger className="w-full sm:w-48">
<SelectValue placeholder="All clients" />
</SelectTrigger>
<SelectContent>
<SelectItem value={ALL}>All clients</SelectItem>
{(tenants || []).map((tenant) => (
<SelectItem key={tenant.tenantid} value={String(tenant.tenantid)}>
{tenant.tenantname || `Tenant #${tenant.tenantid}`}
</SelectItem>
))}
</SelectContent>
</Select>
{/* Row 2: Search, Zone, Batch, Clients, Milers, and Count */}
<div className="flex flex-wrap items-center justify-between gap-3 w-full">
<div className="flex flex-1 flex-wrap items-center gap-2.5 min-w-0">
<SearchInput
value={search}
onChange={setSearch}
placeholder="Search by order, client, miler, customer..."
className="w-full sm:w-64 lg:w-72"
/>
<ZoneSelector className="w-full sm:w-auto shrink-0" />
<Select value={riderId} onValueChange={setRiderId}>
<SelectTrigger className="w-full sm:w-48">
<SelectValue placeholder="All milers" />
</SelectTrigger>
<SelectContent>
<SelectItem value={ALL}>All milers</SelectItem>
{(riders || []).map((rider) => (
<SelectItem key={rider.userid} value={String(rider.userid)}>
{rider.label}
</SelectItem>
))}
</SelectContent>
</Select>
</ListToolbar>
<Select value={batch} onValueChange={setBatch}>
<SelectTrigger className="w-full sm:w-auto min-w-[210px] h-10">
<div className="flex items-center gap-2 min-w-0">
<Clock className="h-3.5 w-3.5 text-ink-3 shrink-0" />
<SelectValue />
</div>
</SelectTrigger>
<SelectContent className="w-[300px] p-1.5 shadow-lg rounded-xl">
{BATCH_OPTIONS.map((option) => (
<SelectItem
key={option.id}
value={option.id}
textValue={option.id === 'all' ? 'All batches' : `${option.label} · ${option.range}`}
className="py-2 pl-2.5 pr-8 rounded-lg cursor-pointer my-0.5"
>
<div className="flex items-center justify-between gap-3 w-full">
<span className="font-medium text-ink-1 text-sm">{option.label}</span>
<span className="text-[11px] font-medium px-2 py-0.5 rounded-md bg-surface-sunken text-ink-3 border border-border/50 shrink-0">
{option.range}
</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
<Select value={tenantId} onValueChange={setTenantId}>
<SelectTrigger className="w-full sm:w-44">
<SelectValue placeholder="All clients" />
</SelectTrigger>
<SelectContent>
<SelectItem value={ALL}>All clients</SelectItem>
{(tenants || []).map((tenant) => (
<SelectItem key={tenant.tenantid} value={String(tenant.tenantid)}>
{tenant.tenantname || `Tenant #${tenant.tenantid}`}
</SelectItem>
))}
</SelectContent>
</Select>
<Select value={riderId} onValueChange={setRiderId}>
<SelectTrigger className="w-full sm:w-44">
<SelectValue placeholder="All milers" />
</SelectTrigger>
<SelectContent>
<SelectItem value={ALL}>All milers</SelectItem>
{(riders || []).map((rider) => (
<SelectItem key={rider.userid} value={String(rider.userid)}>
{rider.label}
</SelectItem>
))}
</SelectContent>
</Select>
{isFiltered && (
<Button
variant="ghost"
size="sm"
onClick={() => {
setSearch('');
setBatch('all');
setTenantId(ALL);
setRiderId(ALL);
}}
className="text-xs h-9 text-brand hover:text-brand-hover hover:bg-brand-soft"
>
Clear filters
</Button>
)}
</div>
<div className="text-caption text-ink-3 shrink-0">
{rows.length === batchRows.length
? `${batchRows.length} ${batchRows.length === 1 ? 'delivery' : 'deliveries'}`
: `${rows.length} of ${batchRows.length} deliveries`}
</div>
</div>
<DataTable
columns={columns}

View File

@@ -209,14 +209,6 @@ export default function Exceptions() {
return (
<Stack space="lg">
<PageHeader
actions={
<Button onClick={openCreate}>
<Plus className="mr-1.5 h-4 w-4" /> Log exception
</Button>
}
/>
<Grid cols={4}>
<KpiCard
icon={ClipboardList}
@@ -262,6 +254,11 @@ export default function Exceptions() {
onSearchChange={setSearch}
placeholder="Search by consignment, type or description"
count={{ shown: rows.length, total: exceptions.length, noun: 'exceptions' }}
actions={
<Button onClick={openCreate}>
<Plus className="mr-1.5 h-4 w-4" /> Log exception
</Button>
}
/>
<DataTable

View File

@@ -198,14 +198,6 @@ export default function Hubs() {
return (
<Stack space="lg">
<PageHeader
actions={
<Button onClick={openCreate}>
<Plus className="mr-1.5 h-4 w-4" /> New hub
</Button>
}
/>
<Grid cols={4}>
<KpiCard
icon={Warehouse}
@@ -251,6 +243,11 @@ export default function Hubs() {
onSearchChange={setSearch}
placeholder="Search hubs, addresses or pincodes"
count={{ shown: rows.length, total: hubs.length, noun: 'hubs' }}
actions={
<Button onClick={openCreate}>
<Plus className="mr-1.5 h-4 w-4" /> New hub
</Button>
}
/>
<DataTable

View File

@@ -437,24 +437,12 @@ export default function Orders() {
return (
<Stack space="lg">
<PageHeader
actions={
<>
<Button variant="outline" onClick={() => navigate('/doormile/orders/createorders')}>
<Upload className="mr-1.5 h-4 w-4" /> Bulk upload
</Button>
<Button onClick={() => navigate('/doormile/orders/create')}>
<Plus className="mr-1.5 h-4 w-4" /> New order
</Button>
</>
}
/>
<Stack space="sm">
{/* Both halves used to be `shrink-0` in a non-wrapping row, which came to
1049px of content — on a 375px phone the whole document scrolled
sideways rather than this strip. The row now wraps, and the tab strip
scrolls inside its own track, so every tab stays reachable. */}
{/* Row 1: Tabs on left, Bulk upload & New order on right */}
<div className="flex flex-wrap items-center justify-between gap-3 w-full">
<div className="no-scrollbar -mx-1 max-w-full min-w-0 overflow-x-auto px-1 py-0.5">
<Tabs
@@ -465,7 +453,19 @@ export default function Orders() {
/>
</div>
<div className="flex min-w-0 flex-1 flex-wrap items-center justify-end gap-2 sm:flex-none">
<div className="flex items-center gap-2 shrink-0">
<Button variant="outline" onClick={() => navigate('/doormile/orders/createorders')}>
<Upload className="mr-1.5 h-4 w-4" /> Bulk upload
</Button>
<Button onClick={() => navigate('/doormile/orders/create')}>
<Plus className="mr-1.5 h-4 w-4" /> New order
</Button>
</div>
</div>
{/* Row 2: Search, Date, Zone, and Export */}
<div className="flex flex-wrap items-center justify-between gap-3 w-full">
<div className="flex min-w-0 flex-1 flex-wrap items-center gap-2">
<DatePicker
value={selectedDate}
onChange={setSelectedDate}
@@ -476,8 +476,11 @@ export default function Orders() {
value={search}
onChange={setSearch}
placeholder="Search by order number, address or note"
className="w-full sm:w-64"
className="w-full sm:w-80"
/>
</div>
<div className="flex items-center gap-2">
{selectedIds.length > 0 && tab === 'pending' && (
<Button variant="outline" onClick={() => setCancelOpen(true)}>
<Ban className="mr-1.5 h-4 w-4" /> Cancel {selectedIds.length}

View File

@@ -191,22 +191,18 @@ export default function OrdersPreview() {
return (
<Stack space="lg">
<PageHeader
actions={
<>
<Button variant="outline" onClick={() => navigate('/doormile/orders')}>
<ArrowLeft className="mr-1.5 h-4 w-4" /> Back to orders
</Button>
<Button
onClick={() => commit.mutate({ deliveries: rows })}
loading={commit.isPending}
disabled={rows.length === 0}
>
<Send className="mr-1.5 h-4 w-4" /> Assign {rows.length || ''} orders
</Button>
</>
}
/>
<div className="flex flex-wrap items-center justify-between gap-3">
<Button variant="outline" onClick={() => navigate('/doormile/orders')}>
<ArrowLeft className="mr-1.5 h-4 w-4" /> Back to orders
</Button>
<Button
onClick={() => commit.mutate({ deliveries: rows })}
loading={commit.isPending}
disabled={rows.length === 0}
>
<Send className="mr-1.5 h-4 w-4" /> Assign {rows.length || ''} orders
</Button>
</div>
<Grid cols={4}>
<KpiCard icon={Package} value={number(stats.orders, '0')} label="Orders" tone="brand" index={0} />

View File

@@ -254,19 +254,6 @@ export default function Pricing() {
return (
<Stack space="lg">
<PageHeader
actions={
<>
<Button variant="outline" onClick={() => setQuoteOpen(true)}>
<Calculator className="mr-1.5 h-4 w-4" /> Get a quote
</Button>
<Button onClick={openCreate}>
<Plus className="mr-1.5 h-4 w-4" /> New rule
</Button>
</>
}
/>
<Grid cols={4}>
<KpiCard icon={Tag} value={stats.total} label="Pricing rules" tone="brand" index={0} loading={isLoading} />
<KpiCard
@@ -294,6 +281,16 @@ export default function Pricing() {
onSearchChange={setSearch}
placeholder="Search by client, zone or vehicle"
count={{ shown: rows.length, total: zonePricing.length, noun: 'rules' }}
actions={
<>
<Button variant="outline" onClick={() => setQuoteOpen(true)}>
<Calculator className="mr-1.5 h-4 w-4" /> Get a quote
</Button>
<Button onClick={openCreate}>
<Plus className="mr-1.5 h-4 w-4" /> New rule
</Button>
</>
}
>
<Select value={String(zone)} onValueChange={setZone}>
<SelectTrigger className="w-full sm:w-48">

View File

@@ -161,27 +161,6 @@ export default function OrdersDetails() {
return (
<Stack space="lg">
<PageHeader
actions={
<>
<Input
type="date"
value={range.from}
onChange={(e) => setRange((current) => ({ ...current, from: e.target.value }))}
aria-label="From date"
className="w-40"
/>
<Input
type="date"
value={range.to}
onChange={(e) => setRange((current) => ({ ...current, to: e.target.value }))}
aria-label="To date"
className="w-40"
/>
</>
}
/>
<Stack space="sm">
<ListToolbar
search={search}
@@ -198,6 +177,23 @@ export default function OrdersDetails() {
</Button>
}
>
<div className="flex items-center gap-1.5">
<Input
type="date"
value={range.from}
onChange={(e) => setRange((current) => ({ ...current, from: e.target.value }))}
aria-label="From date"
className="w-36"
/>
<span className="text-xs text-ink-4">to</span>
<Input
type="date"
value={range.to}
onChange={(e) => setRange((current) => ({ ...current, to: e.target.value }))}
aria-label="To date"
className="w-36"
/>
</div>
<Select value={tenantId} onValueChange={setTenantId}>
<SelectTrigger className="w-full sm:w-48">
<SelectValue placeholder="All clients" />

View File

@@ -168,27 +168,6 @@ export default function OrdersSummary() {
return (
<Stack space="lg">
<PageHeader
actions={
<>
<Input
type="date"
value={range.from}
onChange={(e) => setRange((current) => ({ ...current, from: e.target.value }))}
aria-label="From date"
className="w-40"
/>
<Input
type="date"
value={range.to}
onChange={(e) => setRange((current) => ({ ...current, to: e.target.value }))}
aria-label="To date"
className="w-40"
/>
</>
}
/>
<Grid cols={4}>
<KpiCard icon={Package} value={number(stats.totalOrders, '0')} label="Total orders" tone="brand" index={0} loading={query.isLoading} />
<KpiCard
@@ -233,6 +212,23 @@ export default function OrdersSummary() {
</Button>
}
>
<div className="flex items-center gap-1.5">
<Input
type="date"
value={range.from}
onChange={(e) => setRange((current) => ({ ...current, from: e.target.value }))}
aria-label="From date"
className="w-36"
/>
<span className="text-xs text-ink-4">to</span>
<Input
type="date"
value={range.to}
onChange={(e) => setRange((current) => ({ ...current, to: e.target.value }))}
aria-label="To date"
className="w-36"
/>
</div>
<Select value={zone} onValueChange={setZone}>
<SelectTrigger className="w-full sm:w-40">
<SelectValue placeholder="All zones" />

View File

@@ -118,27 +118,6 @@ export default function RidersSummary() {
return (
<Stack space="lg">
<PageHeader
actions={
<>
<Input
type="date"
value={range.from}
onChange={(e) => setRange((current) => ({ ...current, from: e.target.value }))}
aria-label="From date"
className="w-40"
/>
<Input
type="date"
value={range.to}
onChange={(e) => setRange((current) => ({ ...current, to: e.target.value }))}
aria-label="To date"
className="w-40"
/>
</>
}
/>
<Grid cols={4}>
<KpiCard icon={Bike} value={number(stats.riders, '0')} label="Milers active" tone="brand" index={0} loading={isLoading} />
<KpiCard icon={Package} value={number(stats.orders, '0')} label="Orders assigned" tone="navy" index={1} loading={isLoading} />
@@ -176,6 +155,23 @@ export default function RidersSummary() {
</Button>
}
>
<div className="flex items-center gap-1.5">
<Input
type="date"
value={range.from}
onChange={(e) => setRange((current) => ({ ...current, from: e.target.value }))}
aria-label="From date"
className="w-36"
/>
<span className="text-xs text-ink-4">to</span>
<Input
type="date"
value={range.to}
onChange={(e) => setRange((current) => ({ ...current, to: e.target.value }))}
aria-label="To date"
className="w-36"
/>
</div>
<Select value={zone} onValueChange={setZone}>
<SelectTrigger className="w-full sm:w-44">
<SelectValue placeholder="All zones" />

View File

@@ -260,14 +260,6 @@ export default function Riders() {
return (
<Stack space="lg">
<PageHeader
actions={
<Button onClick={() => navigate('/doormile/milers/create')}>
<Plus className="mr-1.5 h-4 w-4" /> New miler
</Button>
}
/>
<Grid cols={4}>
<KpiCard
icon={Users}
@@ -333,6 +325,9 @@ export default function Riders() {
placeholder="Search by name, phone, id or city"
className="w-full sm:w-64"
/>
<Button onClick={() => navigate('/doormile/milers/create')}>
<Plus className="mr-1.5 h-4 w-4" /> New miler
</Button>
</div>
</div>

View File

@@ -92,14 +92,6 @@ export default function Settings() {
className="w-44 bg-white shadow-xs"
/>
)}
{activeTab === 'customers' && (
<Button
onClick={() => navigate('/doormile/customer/create')}
className="inline-flex items-center gap-1.5"
>
<Plus className="h-4 w-4" /> New customer
</Button>
)}
</div>
</div>

View File

@@ -195,14 +195,6 @@ export default function Tripsheets() {
return (
<Stack space="lg">
<PageHeader
actions={
<Button onClick={openCreate}>
<Plus className="mr-1.5 h-4 w-4" /> New tripsheet
</Button>
}
/>
<Grid cols={4}>
<KpiCard
icon={Truck}
@@ -248,6 +240,11 @@ export default function Tripsheets() {
onSearchChange={setSearch}
placeholder="Search by tripsheet id, hub or status"
count={{ shown: rows.length, total: tripsheets.length, noun: 'tripsheets' }}
actions={
<Button onClick={openCreate}>
<Plus className="mr-1.5 h-4 w-4" /> New tripsheet
</Button>
}
/>
<DataTable

View File

@@ -208,14 +208,6 @@ export default function Vehicles() {
return (
<Stack space="lg">
<PageHeader
actions={
<Button onClick={openCreate}>
<Plus className="mr-1.5 h-4 w-4" /> New vehicle
</Button>
}
/>
<Grid cols={4}>
<KpiCard
icon={Car}
@@ -261,6 +253,11 @@ export default function Vehicles() {
onSearchChange={setSearch}
placeholder="Search by registration, type or status"
count={{ shown: rows.length, total: vehicles.length, noun: 'vehicles' }}
actions={
<Button onClick={openCreate}>
<Plus className="mr-1.5 h-4 w-4" /> New vehicle
</Button>
}
/>
<DataTable