updates on the pricing page and navbar updates
This commit is contained in:
@@ -56,6 +56,9 @@ const NAV = [
|
|||||||
{ label: 'Deliveries', path: '/doormile/deliveries' },
|
{ label: 'Deliveries', path: '/doormile/deliveries' },
|
||||||
{ label: 'Milers', path: '/doormile/milers' },
|
{ label: 'Milers', path: '/doormile/milers' },
|
||||||
{ label: 'Clients', path: '/doormile/tenants' },
|
{ label: 'Clients', path: '/doormile/tenants' },
|
||||||
|
// Staff manage every client's rate card here; a client login sees its own,
|
||||||
|
// read-only (the server scopes the list and refuses a client's changes).
|
||||||
|
{ label: 'Pricing', path: '/doormile/pricing' },
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Destinations grouped behind a menu, in the order the console lists them. */
|
/** Destinations grouped behind a menu, in the order the console lists them. */
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
useTenants, useUpdatePricing,
|
useTenants, useUpdatePricing,
|
||||||
} from '@/lib/doormileHooks';
|
} from '@/lib/doormileHooks';
|
||||||
import { currency, matchesQuery, orDash, useDebouncedValue } from '@/lib/doormileFormat';
|
import { currency, matchesQuery, orDash, useDebouncedValue } from '@/lib/doormileFormat';
|
||||||
|
import { useAuth } from '@/lib/AuthContext';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client pricing — the per-tenant rate cards quotes are calculated from.
|
* Client pricing — the per-tenant rate cards quotes are calculated from.
|
||||||
@@ -18,6 +19,11 @@ import { currency, matchesQuery, orDash, useDebouncedValue } from '@/lib/doormil
|
|||||||
* This is `/admin/pricing`, the tenant-scoped rules. Doormile's own internal
|
* This is `/admin/pricing`, the tenant-scoped rules. Doormile's own internal
|
||||||
* banding lives on a separate resource with no tenant concept at all and is not
|
* banding lives on a separate resource with no tenant concept at all and is not
|
||||||
* edited here.
|
* edited here.
|
||||||
|
*
|
||||||
|
* A client login sees its own rate card read-only: the server scopes the list
|
||||||
|
* to its tenant and refuses a client's create, edit, delete and quote (the
|
||||||
|
* quote picks a rule across every tenant, so it would show another client's
|
||||||
|
* rates).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const VEHICLE_TYPES = ['Bike', 'Scooter', 'Bicycle', 'Car', 'Van'];
|
const VEHICLE_TYPES = ['Bike', 'Scooter', 'Bicycle', 'Car', 'Van'];
|
||||||
@@ -48,6 +54,7 @@ const EMPTY_QUOTE = { tenantid: '', applocationid: '', vehicletype: 'Bike', weig
|
|||||||
const ALL_ZONES = 'all';
|
const ALL_ZONES = 'all';
|
||||||
|
|
||||||
export default function Pricing() {
|
export default function Pricing() {
|
||||||
|
const { isClient } = useAuth();
|
||||||
const { data: pricing = [], isLoading, isFetching } = usePricing();
|
const { data: pricing = [], isLoading, isFetching } = usePricing();
|
||||||
const { data: tenants = [] } = useTenants();
|
const { data: tenants = [] } = useTenants();
|
||||||
const { data: locations = [] } = useAppLocations();
|
const { data: locations = [] } = useAppLocations();
|
||||||
@@ -234,7 +241,7 @@ export default function Pricing() {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
cell: (row) => <StatusBadge status={row.status} dot size="sm" />,
|
cell: (row) => <StatusBadge status={row.status} dot size="sm" />,
|
||||||
},
|
},
|
||||||
{
|
!isClient && {
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
header: 'Actions',
|
header: 'Actions',
|
||||||
align: 'right',
|
align: 'right',
|
||||||
@@ -245,9 +252,9 @@ export default function Pricing() {
|
|||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
].filter(Boolean),
|
||||||
|
|
||||||
[tenantMap, zoneMap]
|
[tenantMap, zoneMap, isClient]
|
||||||
);
|
);
|
||||||
|
|
||||||
const quoteResult = simulate.data?.data || simulate.data;
|
const quoteResult = simulate.data?.data || simulate.data;
|
||||||
@@ -282,6 +289,7 @@ export default function Pricing() {
|
|||||||
placeholder="Search by client, zone or vehicle"
|
placeholder="Search by client, zone or vehicle"
|
||||||
count={{ shown: rows.length, total: zonePricing.length, noun: 'rules' }}
|
count={{ shown: rows.length, total: zonePricing.length, noun: 'rules' }}
|
||||||
actions={
|
actions={
|
||||||
|
isClient ? null : (
|
||||||
<>
|
<>
|
||||||
<Button variant="outline" onClick={() => setQuoteOpen(true)}>
|
<Button variant="outline" onClick={() => setQuoteOpen(true)}>
|
||||||
<Calculator className="mr-1.5 h-4 w-4" /> Get a quote
|
<Calculator className="mr-1.5 h-4 w-4" /> Get a quote
|
||||||
@@ -290,6 +298,7 @@ export default function Pricing() {
|
|||||||
<Plus className="mr-1.5 h-4 w-4" /> New rule
|
<Plus className="mr-1.5 h-4 w-4" /> New rule
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Select value={String(zone)} onValueChange={setZone}>
|
<Select value={String(zone)} onValueChange={setZone}>
|
||||||
@@ -327,8 +336,12 @@ export default function Pricing() {
|
|||||||
<EmptyState
|
<EmptyState
|
||||||
icon={Tag}
|
icon={Tag}
|
||||||
title="No pricing rules yet"
|
title="No pricing rules yet"
|
||||||
description="Add a rate card so this client's deliveries can be quoted."
|
description={
|
||||||
action={{ label: 'New rule', icon: Plus, onClick: openCreate }}
|
isClient
|
||||||
|
? 'Doormile has not set up your rate card yet. Contact your Doormile account manager.'
|
||||||
|
: "Add a rate card so this client's deliveries can be quoted."
|
||||||
|
}
|
||||||
|
action={isClient ? undefined : { label: 'New rule', icon: Plus, onClick: openCreate }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user