updates on the ui changes and table
This commit is contained in:
@@ -17,7 +17,6 @@ import {
|
||||
import { Grid } from '@astryxdesign/core/Grid';
|
||||
import { HStack } from '@astryxdesign/core/HStack';
|
||||
import { VStack } from '@astryxdesign/core/VStack';
|
||||
import { Card } from '@astryxdesign/core/Card';
|
||||
import { Text } from '@astryxdesign/core/Text';
|
||||
import { Button } from '@astryxdesign/core/Button';
|
||||
import { IconButton } from '@astryxdesign/core/IconButton';
|
||||
@@ -34,6 +33,7 @@ import TableLoader from 'components/nearle_components/TableLoader';
|
||||
import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
|
||||
import PageHeader from 'components/nearle_components/PageHeader';
|
||||
import StatCard from 'components/nearle_components/StatCard';
|
||||
import { PageShell, KpiGrid, FilterToolbar, DataCard } from 'components/nearle_components/PageLayout';
|
||||
import AddressAutocomplete from 'components/nearle_components/AddressAutocomplete';
|
||||
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
|
||||
import { getAdminCustomers, updateAdminCustomer } from 'pages/api/doormileApi';
|
||||
@@ -162,55 +162,66 @@ const Customers = () => {
|
||||
<>
|
||||
{isLoading && <Loader />}
|
||||
|
||||
<PageHeader
|
||||
title="Customers"
|
||||
subtitle="Live · B2C Directory"
|
||||
live
|
||||
action={
|
||||
<Tooltip content="Adds a contact to your tenant's own customer book (used when creating orders) — separate from the order-derived B2C directory below">
|
||||
<Button label="Add Customer" variant="primary" icon={<MdPersonAddAlt1 size={16} />} onClick={() => navigate('/doormile/customer/create')} />
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
|
||||
<Grid columns={{ minWidth: 200, max: 4, repeat: 'fit' }} gap={3}>
|
||||
<StatCard title="Total Customers" value={stats.total} icon={<MdOutlinePeopleAlt size={20} />} color={BRAND} />
|
||||
<StatCard title="With Email" value={stats.withEmail} icon={<MdOutlineMailOutline size={20} />} color="#0ea5e9" />
|
||||
<StatCard title="With Phone" value={stats.withPhone} icon={<MdOutlinePhoneAndroid size={20} />} color="#8b5cf6" />
|
||||
<StatCard title="Total Bookings" value={stats.totalBookings} icon={<MdOutlineReceiptLong size={20} />} color="#10b981" />
|
||||
</Grid>
|
||||
|
||||
<VStack gap={0} padding={0} style={{ marginTop: 16 }}>
|
||||
<Card padding={3} elevation="none" style={{ borderBottomLeftRadius: 0, borderBottomRightRadius: 0, borderBottom: 'none' }}>
|
||||
<HStack justify="between" vAlign="center" gap={3} wrap="wrap">
|
||||
<HStack gap={2} vAlign="center">
|
||||
<AccentAvatar color={BRAND} size={32}>
|
||||
<MdPeopleAlt size={18} />
|
||||
</AccentAvatar>
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text type="label" weight="bold" color="secondary" style={{ letterSpacing: 0.6, textTransform: 'uppercase' }}>
|
||||
Directory
|
||||
</Text>
|
||||
<Text weight="bold">
|
||||
{customers.length} total · {rows.length} shown
|
||||
</Text>
|
||||
</VStack>
|
||||
</HStack>
|
||||
<div style={{ width: 280, maxWidth: '100%' }}>
|
||||
<DebounceSearchBar
|
||||
value={searchword}
|
||||
onChange={setSearchword}
|
||||
onDebouncedChange={setDebouncedSearch}
|
||||
placeholder="Search customers (ctrl+k)"
|
||||
<PageShell>
|
||||
<PageHeader
|
||||
title="Customers"
|
||||
subtitle="Live · B2C Directory"
|
||||
live
|
||||
action={
|
||||
<Tooltip content="Adds a contact to your tenant's own customer book (used when creating orders) — separate from the order-derived B2C directory below">
|
||||
<Button
|
||||
label="Add Customer"
|
||||
variant="primary"
|
||||
icon={<MdPersonAddAlt1 size={16} />}
|
||||
onClick={() => navigate('/doormile/customer/create')}
|
||||
/>
|
||||
</div>
|
||||
</HStack>
|
||||
</Card>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
|
||||
<Card padding={0} elevation="none" style={{ borderTopLeftRadius: 0, borderTopRightRadius: 0, overflow: 'hidden' }}>
|
||||
<KpiGrid>
|
||||
<StatCard title="Total Customers" value={stats.total} icon={<MdOutlinePeopleAlt size={20} />} color={BRAND} />
|
||||
<StatCard title="With Email" value={stats.withEmail} icon={<MdOutlineMailOutline size={20} />} color="#0ea5e9" />
|
||||
<StatCard title="With Phone" value={stats.withPhone} icon={<MdOutlinePhoneAndroid size={20} />} color="#8b5cf6" />
|
||||
<StatCard title="Total Bookings" value={stats.totalBookings} icon={<MdOutlineReceiptLong size={20} />} color="#10b981" />
|
||||
</KpiGrid>
|
||||
|
||||
<DataCard
|
||||
toolbar={
|
||||
<FilterToolbar max={2}>
|
||||
<HStack gap={2} vAlign="center">
|
||||
<AccentAvatar color={BRAND} size={32}>
|
||||
<MdPeopleAlt size={18} />
|
||||
</AccentAvatar>
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text type="label" weight="bold" color="secondary" style={{ letterSpacing: 0.6, textTransform: 'uppercase' }}>
|
||||
Directory
|
||||
</Text>
|
||||
<Text weight="bold">
|
||||
{customers.length} total · {rows.length} shown
|
||||
</Text>
|
||||
</VStack>
|
||||
</HStack>
|
||||
<HStack justify="end">
|
||||
<VStack width={260} padding={0} gap={0}>
|
||||
<DebounceSearchBar
|
||||
value={searchword}
|
||||
onChange={setSearchword}
|
||||
onDebouncedChange={setDebouncedSearch}
|
||||
placeholder="Search customers (ctrl+k)"
|
||||
/>
|
||||
</VStack>
|
||||
</HStack>
|
||||
</FilterToolbar>
|
||||
}
|
||||
>
|
||||
{isMobile ? (
|
||||
rows.length === 0 && !isLoading ? (
|
||||
<EmptyState title="No customers to show" description="Customers appear here once they've placed an order." icon={<MdPeopleAlt size={28} />} />
|
||||
<EmptyState
|
||||
title="No customers to show"
|
||||
description="Customers appear here once they've placed an order."
|
||||
icon={<MdPeopleAlt size={28} />}
|
||||
/>
|
||||
) : (
|
||||
<MobileCardList scroll>
|
||||
{rows.map((row, index) => (
|
||||
@@ -232,7 +243,14 @@ const Customers = () => {
|
||||
</Text>
|
||||
</VStack>
|
||||
</HStack>
|
||||
<IconButton label="Edit customer" tooltip="Edit customer" size="sm" variant="ghost" icon={<MdEdit size={14} />} onClick={() => openEdit(row)} />
|
||||
<IconButton
|
||||
label="Edit customer"
|
||||
tooltip="Edit customer"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
icon={<MdEdit size={14} />}
|
||||
onClick={() => openEdit(row)}
|
||||
/>
|
||||
</HStack>
|
||||
}
|
||||
>
|
||||
@@ -262,7 +280,11 @@ const Customers = () => {
|
||||
{rows.length === 0 && !isLoading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5}>
|
||||
<EmptyState title="No customers to show" description="Customers appear here once they've placed an order." icon={<MdPeopleAlt size={28} />} />
|
||||
<EmptyState
|
||||
title="No customers to show"
|
||||
description="Customers appear here once they've placed an order."
|
||||
icon={<MdPeopleAlt size={28} />}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
@@ -305,7 +327,14 @@ const Customers = () => {
|
||||
<TableCell>{custBookings(row)}</TableCell>
|
||||
<TableCell>
|
||||
<HStack justify="end">
|
||||
<IconButton label="Edit customer" tooltip="Edit customer" size="sm" variant="ghost" icon={<MdEdit size={16} />} onClick={() => openEdit(row)} />
|
||||
<IconButton
|
||||
label="Edit customer"
|
||||
tooltip="Edit customer"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
icon={<MdEdit size={16} />}
|
||||
onClick={() => openEdit(row)}
|
||||
/>
|
||||
</HStack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -315,8 +344,8 @@ const Customers = () => {
|
||||
</Table>
|
||||
</TableScroll>
|
||||
)}
|
||||
</Card>
|
||||
</VStack>
|
||||
</DataCard>
|
||||
</PageShell>
|
||||
|
||||
<Dialog isOpen={!!editRow} onOpenChange={(next) => !next && setEditRow(null)} width={760} purpose="form">
|
||||
<Layout
|
||||
@@ -335,7 +364,7 @@ const Customers = () => {
|
||||
content={
|
||||
<LayoutContent padding={4}>
|
||||
<VStack gap={4} padding={0}>
|
||||
<Grid columns={{ minWidth: 220 }} gap={3}>
|
||||
<Grid columns={{ minWidth: 220, repeat: 'fit' }} gap={3}>
|
||||
<TextInput label="Customer Name" value={form.name || ''} onChange={(v) => setForm((f) => ({ ...f, name: v }))} />
|
||||
<TextInput
|
||||
label="Contact Number"
|
||||
@@ -343,23 +372,48 @@ const Customers = () => {
|
||||
onChange={(v) => setForm((f) => ({ ...f, phone: v.replace(/\D/g, '').slice(0, 10) }))}
|
||||
startIcon={MdOutlinePhoneAndroid}
|
||||
/>
|
||||
<TextInput label="Email" type="email" value={form.email || ''} onChange={(v) => setForm((f) => ({ ...f, email: v }))} startIcon={MdMail} />
|
||||
<TextInput
|
||||
label="Email"
|
||||
type="email"
|
||||
value={form.email || ''}
|
||||
onChange={(v) => setForm((f) => ({ ...f, email: v }))}
|
||||
startIcon={MdMail}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Text type="supporting" color="secondary" style={{ fontStyle: 'italic' }}>
|
||||
The backend doesn't store an address against a customer yet — these fields aren't required and won't be saved on submit.
|
||||
The backend doesn't store an address against a customer yet — these fields aren't required and won't be
|
||||
saved on submit.
|
||||
</Text>
|
||||
|
||||
<AddressAutocomplete id="edit-customer-address" label="Address" value={addressInput} onChange={setAddressInput} onPlaceSelected={handleAddressPlaceSelected} />
|
||||
<AddressAutocomplete
|
||||
id="edit-customer-address"
|
||||
label="Address"
|
||||
value={addressInput}
|
||||
onChange={setAddressInput}
|
||||
onPlaceSelected={handleAddressPlaceSelected}
|
||||
/>
|
||||
|
||||
<Grid columns={{ minWidth: 200 }} gap={3}>
|
||||
<TextInput label="Location" value={pickAddress.suburb || ''} onChange={(v) => setPickAddress((p) => ({ ...p, suburb: v }))} />
|
||||
<Grid columns={{ minWidth: 200, repeat: 'fit' }} gap={3}>
|
||||
<TextInput
|
||||
label="Location"
|
||||
value={pickAddress.suburb || ''}
|
||||
onChange={(v) => setPickAddress((p) => ({ ...p, suburb: v }))}
|
||||
/>
|
||||
<TextInput label="City" value={pickAddress.city || ''} onChange={(v) => setPickAddress((p) => ({ ...p, city: v }))} />
|
||||
<TextInput label="State" value={pickAddress.state || ''} onChange={(v) => setPickAddress((p) => ({ ...p, state: v }))} />
|
||||
<TextInput label="Postcode" value={pickAddress.postcode || ''} onChange={(v) => setPickAddress((p) => ({ ...p, postcode: v }))} />
|
||||
<TextInput label="Landmark" value={pickAddress.landmark || ''} onChange={(v) => setPickAddress((p) => ({ ...p, landmark: v }))} />
|
||||
<TextInput
|
||||
label="Postcode"
|
||||
value={pickAddress.postcode || ''}
|
||||
onChange={(v) => setPickAddress((p) => ({ ...p, postcode: v }))}
|
||||
/>
|
||||
<TextInput
|
||||
label="Landmark"
|
||||
value={pickAddress.landmark || ''}
|
||||
onChange={(v) => setPickAddress((p) => ({ ...p, landmark: v }))}
|
||||
/>
|
||||
<TextInput label="Latitude" value={latLng.latitude || ''} isDisabled />
|
||||
<TextInput label="Longitude" value={latLng.longitude || ''} isDisabled />
|
||||
</Grid>
|
||||
|
||||
Reference in New Issue
Block a user