updates on the ui changes and table

This commit is contained in:
2026-08-17 15:39:46 +05:30
parent a46212f291
commit 809d046323
38 changed files with 6403 additions and 7019 deletions

View File

@@ -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&apos;t store an address against a customer yet — these fields aren&apos;t required and won&apos;t be saved on submit.
The backend doesn&apos;t store an address against a customer yet — these fields aren&apos;t required and won&apos;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>