inspiren-sem-tool/resources/js/pages/campaigns/index.tsx

213 lines
6.9 KiB
TypeScript

import React, { useMemo } from 'react';
import AppLayout from '../../layouts/app-layout';
import {
Container,
Title,
Group,
Button,
Badge,
ActionIcon,
Stack,
Text,
Tabs,
} from '@mantine/core';
import { IconEye, IconRefresh } from '@tabler/icons-react';
import { MantineReactTable } from 'mantine-react-table';
import type { MRT_Row } from 'mantine-react-table';
import { Link, router } from '@inertiajs/react';
import { Client } from "@/types";
interface Props {
clients: Client[];
googleCompanySyncRunning: boolean;
}
const currencyFormatter = new Intl.NumberFormat('en-MY', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
const parseAmount = (value?: number | string | null): number => {
if (value === undefined || value === null) {
return 0;
}
const normalized = typeof value === 'string' ? Number(value.replace(/,/g, '')) : Number(value);
return Number.isNaN(normalized) ? 0 : normalized;
};
const statusOrder = ['ENABLED', 'PAUSED', 'REMOVED', 'ENDED', 'CANCELED'];
const getStatusColor = (status: string) => {
if (status === 'ENABLED') {
return 'green';
}
if (status === 'PAUSED') {
return 'yellow';
}
if (status === 'ENDED' || status === 'CANCELED' || status === 'REMOVED') {
return 'red';
}
return 'gray';
};
export default function TicketDetails({
clients,
googleCompanySyncRunning,
}: Props) {
const [syncing, setSyncing] = React.useState(false);
const [activeStatus, setActiveStatus] = React.useState<string | null>('all');
const campaignsData = clients ?? [];
const statusTabs = useMemo(() => {
const counts = campaignsData.reduce<Record<string, number>>((acc, client) => {
const status = client.status || 'UNKNOWN';
acc[status] = (acc[status] ?? 0) + 1;
return acc;
}, {});
return Object.entries(counts).sort(([a], [b]) => {
const aIndex = statusOrder.indexOf(a);
const bIndex = statusOrder.indexOf(b);
if (aIndex !== -1 || bIndex !== -1) {
return (aIndex === -1 ? Number.MAX_SAFE_INTEGER : aIndex)
- (bIndex === -1 ? Number.MAX_SAFE_INTEGER : bIndex);
}
return a.localeCompare(b);
});
}, [campaignsData]);
const filteredClients = useMemo(() => {
if (!activeStatus || activeStatus === 'all') {
return campaignsData;
}
return campaignsData.filter((client) => (client.status || 'UNKNOWN') === activeStatus);
}, [activeStatus, campaignsData]);
const columns = useMemo(
() => [
{
accessorKey: 'customer_id',
header: 'Google ID',
},
{
accessorKey: 'sql_acc_code',
header: 'Customer Code',
},
{
accessorKey: 'name',
header: 'Google Account Name',
},
{
accessorKey: 'status',
header: 'Status',
Cell: ({ cell }: any) => {
const value = cell.getValue() as string;
return <Badge color={getStatusColor(value)}>{value}</Badge>;
},
},
{
accessorKey: 'industry',
header: 'Industry',
},
{
accessorKey: 'assigned_person',
header: 'Assigned Person',
Cell: ({ cell }: any) => cell.getValue() ?? '—',
},
{
accessorKey: 'sales_person',
header: 'Sales Person',
Cell: ({ cell }: any) => cell.getValue() ?? '—',
},
],
[]
);
const renderRowActions = ({ row }: { row: MRT_Row<Client> }) => {
const client = row.original;
return (
<Group spacing="xs">
<ActionIcon
color="blue"
component={Link}
href={`/google-ads/accounts/${client.customer_id}`}
>
<IconEye size={18} />
</ActionIcon>
</Group>
);
};
const syncGoogleRecords = () => {
if (syncing || googleCompanySyncRunning) {
return;
}
setSyncing(true);
router.post(
route("google-ads.accounts.sync-google-company-details"),
{},
{
preserveScroll: true,
onFinish: () => setSyncing(false),
},
);
};
return (
<AppLayout>
<Container size="xl" px="xs">
<Group position="apart" mb="md">
<Stack spacing={2}>
<Title order={2}>Clients</Title>
<Text size="sm" color="dimmed">
Sync Google account records before linking pending invoices to new clients.
</Text>
</Stack>
<Button
leftIcon={<IconRefresh size={16} />}
onClick={syncGoogleRecords}
loading={syncing}
disabled={googleCompanySyncRunning}
>
{googleCompanySyncRunning ? "Sync running" : "Sync Google records"}
</Button>
</Group>
<MantineReactTable
columns={columns}
data={filteredClients}
enableRowActions // REQUIRED
positionActionsColumn="last" // optional but recommended
renderRowActions={renderRowActions}
renderTopToolbarCustomActions={() => (
<Tabs value={activeStatus} onTabChange={setActiveStatus}>
<Tabs.List>
<Tabs.Tab value="all">All ({campaignsData.length})</Tabs.Tab>
{statusTabs.map(([status, count]) => (
<Tabs.Tab key={status} value={status}>
<Group spacing={6}>
<Badge color={getStatusColor(status)} variant="dot">
{status}
</Badge>
<Text size="sm" color="dimmed">
{count}
</Text>
</Group>
</Tabs.Tab>
))}
</Tabs.List>
</Tabs>
)}
/>
</Container>
</AppLayout>
);
}