2import { useParams } from "next/navigation";
3import { useEffect, useState } from "react";
4import { apiClient } from "@/lib/client/apiClient";
5import { Line, Doughnut, Bar } from "react-chartjs-2";
20// Register ChartJS components
34interface CustomerData {
45 accountBalance: number;
46 lifetimeSpend: number;
76interface DepartmentSpend {
88export default function CustomerProfilePage() {
89 const params = useParams();
90 const customerId = params.id as string;
92 const [customer, setCustomer] = useState<CustomerData | null>(null);
93 const [recentSales, setRecentSales] = useState<Sale[]>([]);
94 const [allSales, setAllSales] = useState<Sale[]>([]);
95 const [topProducts, setTopProducts] = useState<TopProduct[]>([]);
96 const [contactLogs, setContactLogs] = useState<ContactLog[]>([]);
97 const [departmentSpend, setDepartmentSpend] = useState<DepartmentSpend[]>([]);
98 const [salesTrend, setSalesTrend] = useState<SalesTrend[]>([]);
99 const [loading, setLoading] = useState(true);
100 const [newLogEntry, setNewLogEntry] = useState("");
101 const [logReminder, setLogReminder] = useState("");
102 const [isEditing, setIsEditing] = useState(false);
103 const [editForm, setEditForm] = useState({
112 const [saveMessage, setSaveMessage] = useState<{
113 type: "success" | "error";
119 console.log('Loading customer with ID:', customerId);
127 const loadCustomerData = async () => {
130 console.log('Fetching customer data for ID:', customerId);
132 const result = await apiClient.getCustomers({
134 id: Number(customerId),
138 console.log('Customer API result:', result);
140 if (result.success && result.data && result.data[0]) {
141 const data = result.data[0];
142 console.log('Customer data:', data);
146 name: data.f101 || "",
147 company: data.f154 || "",
148 phone: data.f112 || "",
149 phone2: data.f153 || "",
150 mobile: data.f113 || "",
151 email: data.f115 || "",
152 division: data.f164 || "",
153 accountId: data.f132 || 0,
154 accountName: data.accountName || data.ACCO?.[0]?.f101 || "",
155 accountBalance: data.accountBalance || data.ACCO?.[0]?.f114 || 0,
156 lifetimeSpend: data.f1130 || 0,
157 rank: data.ANA1?.[0]?.f134 || 0,
158 firstSale: data.ANA1?.[0]?.f1100 || "",
159 lastSale: data.ANA1?.[0]?.f1101 || "",
160 totalSales: data.ANA1?.[0]?.f132 || 0,
163 // Initialize edit form
165 name: data.f101 || "",
166 company: data.f154 || "",
167 phone: data.f112 || "",
168 phone2: data.f153 || "",
169 mobile: data.f113 || "",
170 email: data.f115 || "",
171 division: data.f164 || "",
174 console.error('No customer data found in response');
178 console.error("Error loading customer data:", error);
183 const loadRecentSales = async () => {
185 const result = await apiClient.getSales({
186 customerId: Number(customerId),
190 if (result.success && result.data) {
191 const sales: Sale[] = result.data.map((item: any) => ({
192 id: item.f100 || item.f101 || 0,
193 date: item.f102 || item.f101 || "",
194 store: item.f106 || "",
195 total: item.f107 || 0,
196 teller: item.f104 || "",
197 description: item.f201 || "",
199 linePrice: item.f203 || 0,
200 paidBy: item.f301 || "",
202 setRecentSales(sales);
205 console.error("Error loading sales:", error);
209 const loadAnalytics = async () => {
211 // Load product performance stats
212 const statsResult = await apiClient.getCustomerStats({
213 customerId: Number(customerId),
214 type: 'product-performance',
218 console.log('Analytics data:', statsResult);
220 if (statsResult.success && statsResult.data?.APPD) {
221 // Process product data
222 const products = statsResult.data.APPD;
225 const topProds: TopProduct[] = products
227 .map((item: any) => ({
228 name: item.f1203 || `Product ${item.f203}`,
229 quantity: item.f201 || 0,
230 value: item.f200 || 0,
232 setTopProducts(topProds);
234 // Load departments for mapping
235 const deptsResult = await apiClient.getDepartments();
236 const deptMap = new Map<number, string>();
238 if (deptsResult.success && (deptsResult.data as any)?.DATS) {
239 (deptsResult.data as any).DATS.forEach((dept: any) => {
240 deptMap.set(dept.f100, dept.f101);
244 // Process department spending
245 const deptSpendMap = new Map<string, number>();
246 products.forEach((item: any) => {
247 const deptId = item.f1204 || 0;
248 const deptName = deptMap.get(deptId) || `Department ${deptId}`;
249 const value = item.f200 || 0;
250 deptSpendMap.set(deptName, (deptSpendMap.get(deptName) || 0) + value);
254 "#00543b", "#007d57", "#00a874", "#4ade80", "#86efac",
255 "#fbbf24", "#f59e0b", "#f97316", "#ef4444", "#dc2626"
258 const deptArray: DepartmentSpend[] = Array.from(deptSpendMap.entries())
259 .sort((a, b) => b[1] - a[1])
261 .map(([dept, amount], index) => ({
264 color: colors[index % colors.length],
266 setDepartmentSpend(deptArray);
268 // Create sales trend from recent products
269 const trendMap = new Map<string, { amount: number; quantity: number }>();
270 products.slice(0, 20).forEach((item: any, index: number) => {
271 // Use index as pseudo-date for now
272 const label = `Item ${index + 1}`;
273 trendMap.set(label, {
274 amount: item.f200 || 0,
275 quantity: item.f201 || 0
279 const trends: SalesTrend[] = Array.from(trendMap.entries())
280 .map(([date, data]) => ({
283 quantity: data.quantity,
285 setSalesTrend(trends);
289 const brandsResult = await apiClient.getCustomerStats({
290 customerId: Number(customerId),
294 console.log('Brands data:', brandsResult);
297 console.error("Error loading analytics:", error);
301 const loadContactLogs = async () => {
303 const result = await apiClient.getContactLogs({
304 customerId: Number(customerId),
307 if (result.success && result.data) {
308 const logs: ContactLog[] = result.data.map((item: any) => ({
309 date: item.f110 || "",
310 message: item.f120 || "",
312 setContactLogs(logs);
315 console.error("Error loading contact logs:", error);
319 const saveContactLog = async () => {
320 if (!newLogEntry.trim()) return;
323 await apiClient.saveContactLog({
324 customerId: Number(customerId),
325 message: newLogEntry,
326 reminder: logReminder,
333 console.error("Error saving contact log:", error);
334 alert("Failed to save contact log");
338 const handleEdit = () => {
342 const handleCancelEdit = () => {
346 company: customer.company,
347 phone: customer.phone,
348 phone2: customer.phone2,
349 mobile: customer.mobile,
350 email: customer.email,
351 division: customer.division,
357 const handleSaveEdit = async () => {
359 setSaveMessage(null);
360 const result = await apiClient.updateCustomer(Number(customerId), editForm);
362 if (result.success) {
363 // Update local customer state
367 company: editForm.company,
368 phone: editForm.phone,
369 phone2: editForm.phone2,
370 mobile: editForm.mobile,
371 email: editForm.email,
372 division: editForm.division,
375 setSaveMessage({ type: "success", text: "Customer updated successfully" });
377 // Clear success message after 3 seconds
378 setTimeout(() => setSaveMessage(null), 3000);
382 text: "Failed to update customer: " + (result.error || "Unknown error"),
386 console.error("Error saving customer:", error);
389 text: "Failed to save customer changes",
394 const formatCurrency = (value: number) => {
395 return new Intl.NumberFormat("en-US", {
398 minimumFractionDigits: 2,
402 const formatDate = (dateStr: string) => {
403 if (!dateStr) return "";
404 const date = new Date(dateStr);
405 return date.toLocaleDateString("en-GB", {
414 <div className="p-6 bg-surface-2 min-h-screen">
415 <div className="animate-pulse space-y-4">
416 <div className="h-8 bg-surface-2 rounded w-1/4"></div>
417 <div className="h-64 bg-surface-2 rounded"></div>
425 <div className="p-6 bg-surface-2 min-h-screen">
426 <div className="bg-red-50 border border-red-200 rounded-lg p-4">
427 <h2 className="text-red-800 font-semibold">Customer Not Found</h2>
428 <p className="text-red-600">
429 The customer with ID {customerId} could not be found.
437 <div className="p-6 bg-surface-2 min-h-screen">
439 <div className="mb-6 flex items-center justify-between">
441 <div className="flex items-center gap-4">
443 href="/pages/customers"
444 className="text-[#00543b] hover:underline"
449 <h1 className="text-3xl font-bold text-text mt-2">
452 {customer.company && (
453 <p className="text-muted text-lg">{customer.company}</p>
456 <div className="flex gap-3 items-center">
459 className={`px-4 py-2 rounded-lg ${
460 saveMessage.type === "success"
461 ? "bg-green-100 text-green-800 border border-green-200"
462 : "bg-red-100 text-red-800 border border-red-200"
471 onClick={handleCancelEdit}
472 className="bg-muted text-white px-6 py-3 rounded-lg hover:bg-muted transition-colors"
477 onClick={handleSaveEdit}
478 className="bg-[#00543b] text-white px-6 py-3 rounded-lg hover:bg-[#003d2b] transition-colors"
486 className="bg-[#00543b] text-white px-6 py-3 rounded-lg hover:bg-[#003d2b] transition-colors"
494 {/* Account Linkage Section */}
495 {customer.accountId > 0 ? (
496 <div className="mb-6 bg-gradient-to-r from-[#00543b] to-[#007a52] rounded-lg shadow-lg p-6 text-white">
497 <div className="flex items-center justify-between">
499 <h2 className="text-sm font-medium text-white/80 mb-1">
500 Linked Customer Account
502 <div className="flex items-center gap-3">
504 href={`/pages/customers/customer-accounts?account=${customer.accountId}`}
505 className="text-2xl font-bold text-white hover:underline"
507 {customer.accountName || `Account #${customer.accountId}`}
509 <span className="bg-surface/20 px-3 py-1 rounded-full text-sm">
510 Account ID: {customer.accountId}
514 <div className="text-right">
515 <div className="text-sm text-white/80 mb-1">Account Balance</div>
516 <div className={`text-2xl font-bold ${
517 customer.accountBalance > 0
519 : customer.accountBalance < 0
523 {formatCurrency(customer.accountBalance)}
529 <div className="mb-6 bg-surface-2 border-2 border-dashed border-border rounded-lg p-6">
530 <div className="flex items-center gap-3">
531 <div className="flex-shrink-0 w-12 h-12 bg-surface-2 rounded-full flex items-center justify-center">
533 className="w-6 h-6 text-muted/70"
535 stroke="currentColor"
539 strokeLinecap="round"
540 strokeLinejoin="round"
542 d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
547 <h3 className="font-semibold text-text">No Account Linked</h3>
548 <p className="text-muted text-sm">
549 This customer is not linked to a customer account
556 <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
558 <div className="lg:col-span-2 space-y-6">
559 {/* Customer Details Card */}
560 <div className="bg-surface rounded-lg shadow p-6">
561 <h2 className="text-xl font-bold text-text mb-4">
564 <div className="grid grid-cols-2 gap-4">
565 <div className="col-span-2">
566 <div className="text-sm font-medium text-muted mb-1">
569 <div className="text-text">{customer.id}</div>
571 <div className="col-span-2">
572 <label className="text-sm font-medium text-muted mb-1 block">
578 value={editForm.name}
580 setEditForm({ ...editForm, name: e.target.value })
582 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
585 <div className="text-text">{customer.name}</div>
588 <div className="col-span-2">
589 <label className="text-sm font-medium text-muted mb-1 block">
595 value={editForm.company}
597 setEditForm({ ...editForm, company: e.target.value })
599 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
602 <div className="text-text">{customer.company || "—"}</div>
606 <label className="text-sm font-medium text-muted mb-1 block">
612 value={editForm.phone}
614 setEditForm({ ...editForm, phone: e.target.value })
616 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
619 <div className="text-text">{customer.phone || "—"}</div>
623 <label className="text-sm font-medium text-muted mb-1 block">
629 value={editForm.phone2}
631 setEditForm({ ...editForm, phone2: e.target.value })
633 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
636 <div className="text-text">{customer.phone2 || "—"}</div>
640 <label className="text-sm font-medium text-muted mb-1 block">
646 value={editForm.mobile}
648 setEditForm({ ...editForm, mobile: e.target.value })
650 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
653 <div className="text-text">{customer.mobile || "—"}</div>
656 <div className="col-span-2">
657 <label className="text-sm font-medium text-muted mb-1 block">
663 value={editForm.email}
665 setEditForm({ ...editForm, email: e.target.value })
667 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
670 <div className="text-text">
673 href={`mailto:${customer.email}`}
674 className="text-[#00543b] hover:underline"
684 <div className="col-span-2">
685 <label className="text-sm font-medium text-muted mb-1 block">
691 value={editForm.division}
693 setEditForm({ ...editForm, division: e.target.value })
695 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
698 <div className="text-text">{customer.division || "—"}</div>
704 {/* Statistics Card */}
705 <div className="bg-surface rounded-lg shadow p-6">
706 <h2 className="text-xl font-bold text-text mb-4">Statistics</h2>
707 <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
708 <div className="bg-surface-2 p-4 rounded-lg">
709 <div className="text-sm font-medium text-muted mb-1">
712 <div className="text-2xl font-bold text-[#00543b]">
713 {formatCurrency(customer.lifetimeSpend)}
716 <div className="bg-surface-2 p-4 rounded-lg">
717 <div className="text-sm font-medium text-muted mb-1">
720 <div className="text-2xl font-bold text-text">
721 {customer.totalSales}
724 {customer.rank > 0 && (
725 <div className="bg-surface-2 p-4 rounded-lg">
726 <div className="text-sm font-medium text-muted mb-1">
729 <div className="text-2xl font-bold text-text">
734 <div className="bg-surface-2 p-4 rounded-lg">
735 <div className="text-sm font-medium text-muted mb-1">
738 <div className="text-lg font-semibold text-text">
739 {formatDate(customer.lastSale)}
745 {/* Analytics Charts */}
746 {salesTrend.length > 0 && (
747 <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
748 {/* Sales Trend Chart */}
749 <div className="bg-surface rounded-lg shadow p-6">
750 <h2 className="text-xl font-bold text-text mb-4">
753 <div className="h-64">
756 labels: salesTrend.map((t) => t.date),
759 label: "Sales Amount",
760 data: salesTrend.map((t) => t.amount),
761 borderColor: "#00543b",
762 backgroundColor: "rgba(0, 84, 59, 0.1)",
770 maintainAspectRatio: false,
778 `$${context.parsed.y?.toFixed(2) || '0.00'}`,
786 callback: (value) => `$${value}`,
795 {/* Department Spending */}
796 {departmentSpend.length > 0 && (
797 <div className="bg-surface rounded-lg shadow p-6">
798 <h2 className="text-xl font-bold text-text mb-4">
799 Spending by Department
801 <div className="h-64">
804 labels: departmentSpend.map((d) => d.department),
807 data: departmentSpend.map((d) => d.amount),
808 backgroundColor: departmentSpend.map(
818 maintainAspectRatio: false,
831 label: (context) => {
832 const label = context.label || "";
833 const value = context.parsed;
834 const total = departmentSpend.reduce(
835 (sum, d) => sum + d.amount,
842 return `${label}: $${value.toFixed(2)} (${percentage}%)`;
856 {topProducts.length > 0 && (
857 <div className="bg-surface rounded-lg shadow p-6">
858 <h2 className="text-xl font-bold text-text mb-4">
859 Top Products Purchased
861 <div className="space-y-3">
862 {topProducts.slice(0, 5).map((product, index) => (
865 className="flex items-center justify-between p-3 bg-surface-2 rounded-lg hover:bg-surface-2 transition-colors"
867 <div className="flex-1">
868 <div className="font-medium text-text">
871 <div className="text-sm text-muted">
872 Quantity: {product.quantity}
875 <div className="text-right">
876 <div className="font-semibold text-[#00543b]">
877 {formatCurrency(product.value)}
879 <div className="text-xs text-muted">
880 {((product.value / customer.lifetimeSpend) * 100).toFixed(1)}% of total
890 <div className="bg-surface rounded-lg shadow p-6">
891 <h2 className="text-xl font-bold text-text mb-4">
894 {recentSales.length > 0 ? (
895 <div className="overflow-x-auto">
896 <table className="w-full">
897 <thead className="bg-surface-2 border-b-2 border-border">
899 <th className="text-left py-3 px-4 font-semibold text-sm text-text">
902 <th className="text-left py-3 px-4 font-semibold text-sm text-text">
905 <th className="text-left py-3 px-4 font-semibold text-sm text-text">
908 <th className="text-right py-3 px-4 font-semibold text-sm text-text">
911 <th className="text-right py-3 px-4 font-semibold text-sm text-text">
916 <tbody className="divide-y divide-gray-200">
917 {recentSales.slice(0, 10).map((sale, index) => (
918 <tr key={index} className="hover:bg-surface-2">
919 <td className="py-3 px-4 text-sm text-text">
920 {formatDate(sale.date)}
922 <td className="py-3 px-4 text-sm">
924 href={`/report/pos/sales/fieldpine/singlesale.htm?sid=${sale.id}`}
925 className="text-[#00543b] hover:underline"
930 <td className="py-3 px-4 text-sm text-text">
933 <td className="py-3 px-4 text-sm text-text text-right">
936 <td className="py-3 px-4 text-sm text-text text-right">
937 {formatCurrency(sale.linePrice)}
945 <div className="text-center py-8 text-muted">
946 No recent sales found
953 <div className="space-y-6">
955 <div className="bg-surface rounded-lg shadow p-6">
956 <h2 className="text-xl font-bold text-text mb-4">
959 <div className="space-y-4">
962 onChange={(e) => setNewLogEntry(e.target.value)}
964 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent resize-none"
965 placeholder="Add a note..."
967 <div className="flex items-center gap-2">
969 type="datetime-local"
971 onChange={(e) => setLogReminder(e.target.value)}
972 className="flex-1 px-3 py-2 text-sm border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
973 placeholder="Reminder"
976 onClick={saveContactLog}
977 className="bg-[#00543b] text-white px-4 py-2 rounded-lg hover:bg-[#003d2b] transition-colors"
984 {/* Recent Contact Logs */}
985 <div className="mt-6 space-y-3 max-h-96 overflow-y-auto">
986 {contactLogs.slice(0, 10).map((log, index) => (
989 className="border-l-4 border-[#00543b] pl-3 py-2 bg-surface-2"
991 <div className="text-xs text-muted mb-1">
992 {formatDate(log.date)}
994 <div className="text-sm text-text">{log.message}</div>
997 {contactLogs.length === 0 && (
998 <div className="text-center text-muted text-sm py-4">
1005 {/* Quick Actions */}
1006 <div className="bg-surface rounded-lg shadow p-6">
1007 <h2 className="text-xl font-bold text-text mb-4">
1010 <div className="space-y-2">
1013 window.location.href = `/pages/customers/${customerId}`;
1015 className="block w-full text-center bg-surface-2 hover:bg-surface-2 text-text px-4 py-2 rounded-lg transition-colors"
1019 {customer.email && (
1021 href={`mailto:${customer.email}`}
1022 className="block w-full text-center bg-surface-2 hover:bg-surface-2 text-text px-4 py-2 rounded-lg transition-colors"
1028 href={`/pages/reports/sales-reports?customer=${customerId}`}
1029 className="block w-full text-center bg-surface-2 hover:bg-surface-2 text-text px-4 py-2 rounded-lg transition-colors"