EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
page.tsx
Go to the documentation of this file.
1"use client";
2import { useParams } from "next/navigation";
3import { useEffect, useState } from "react";
4import { apiClient } from "@/lib/client/apiClient";
5import { Line, Doughnut, Bar } from "react-chartjs-2";
6import {
7 Chart as ChartJS,
8 CategoryScale,
9 LinearScale,
10 PointElement,
11 LineElement,
12 BarElement,
13 ArcElement,
14 Title,
15 Tooltip,
16 Legend,
17 Filler,
18} from "chart.js";
19
20// Register ChartJS components
21ChartJS.register(
22 CategoryScale,
23 LinearScale,
24 PointElement,
25 LineElement,
26 BarElement,
27 ArcElement,
28 Title,
29 Tooltip,
30 Legend,
31 Filler
32);
33
34interface CustomerData {
35 id: number;
36 name: string;
37 company: string;
38 phone: string;
39 phone2: string;
40 mobile: string;
41 email: string;
42 division: string;
43 accountId: number;
44 accountName: string;
45 accountBalance: number;
46 lifetimeSpend: number;
47 rank: number;
48 firstSale: string;
49 lastSale: string;
50 totalSales: number;
51}
52
53interface Sale {
54 id: number;
55 date: string;
56 store: string;
57 total: number;
58 teller: string;
59 description: string;
60 qty: number;
61 linePrice: number;
62 paidBy: string;
63}
64
65interface TopProduct {
66 name: string;
67 quantity: number;
68 value: number;
69}
70
71interface ContactLog {
72 date: string;
73 message: string;
74}
75
76interface DepartmentSpend {
77 department: string;
78 amount: number;
79 color: string;
80}
81
82interface SalesTrend {
83 date: string;
84 amount: number;
85 quantity: number;
86}
87
88export default function CustomerProfilePage() {
89 const params = useParams();
90 const customerId = params.id as string;
91
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({
104 name: "",
105 company: "",
106 phone: "",
107 phone2: "",
108 mobile: "",
109 email: "",
110 division: "",
111 });
112 const [saveMessage, setSaveMessage] = useState<{
113 type: "success" | "error";
114 text: string;
115 } | null>(null);
116
117 useEffect(() => {
118 if (customerId) {
119 console.log('Loading customer with ID:', customerId);
120 loadCustomerData();
121 loadRecentSales();
122 loadContactLogs();
123 loadAnalytics();
124 }
125 }, [customerId]);
126
127 const loadCustomerData = async () => {
128 try {
129 setLoading(true);
130 console.log('Fetching customer data for ID:', customerId);
131
132 const result = await apiClient.getCustomers({
133 type: "single",
134 id: Number(customerId),
135 source: 'elink',
136 });
137
138 console.log('Customer API result:', result);
139
140 if (result.success && result.data && result.data[0]) {
141 const data = result.data[0];
142 console.log('Customer data:', data);
143
144 setCustomer({
145 id: data.f100 || 0,
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,
161 });
162
163 // Initialize edit form
164 setEditForm({
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 || "",
172 });
173 } else {
174 console.error('No customer data found in response');
175 }
176 setLoading(false);
177 } catch (error) {
178 console.error("Error loading customer data:", error);
179 setLoading(false);
180 }
181 };
182
183 const loadRecentSales = async () => {
184 try {
185 const result = await apiClient.getSales({
186 customerId: Number(customerId),
187 limit: 20,
188 });
189
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 || "",
198 qty: item.f202 || 0,
199 linePrice: item.f203 || 0,
200 paidBy: item.f301 || "",
201 }));
202 setRecentSales(sales);
203 }
204 } catch (error) {
205 console.error("Error loading sales:", error);
206 }
207 };
208
209 const loadAnalytics = async () => {
210 try {
211 // Load product performance stats
212 const statsResult = await apiClient.getCustomerStats({
213 customerId: Number(customerId),
214 type: 'product-performance',
215 limit: 30
216 });
217
218 console.log('Analytics data:', statsResult);
219
220 if (statsResult.success && statsResult.data?.APPD) {
221 // Process product data
222 const products = statsResult.data.APPD;
223
224 // Get top products
225 const topProds: TopProduct[] = products
226 .slice(0, 15)
227 .map((item: any) => ({
228 name: item.f1203 || `Product ${item.f203}`,
229 quantity: item.f201 || 0,
230 value: item.f200 || 0,
231 }));
232 setTopProducts(topProds);
233
234 // Load departments for mapping
235 const deptsResult = await apiClient.getDepartments();
236 const deptMap = new Map<number, string>();
237
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);
241 });
242 }
243
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);
251 });
252
253 const colors = [
254 "#00543b", "#007d57", "#00a874", "#4ade80", "#86efac",
255 "#fbbf24", "#f59e0b", "#f97316", "#ef4444", "#dc2626"
256 ];
257
258 const deptArray: DepartmentSpend[] = Array.from(deptSpendMap.entries())
259 .sort((a, b) => b[1] - a[1])
260 .slice(0, 10)
261 .map(([dept, amount], index) => ({
262 department: dept,
263 amount,
264 color: colors[index % colors.length],
265 }));
266 setDepartmentSpend(deptArray);
267
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
276 });
277 });
278
279 const trends: SalesTrend[] = Array.from(trendMap.entries())
280 .map(([date, data]) => ({
281 date,
282 amount: data.amount,
283 quantity: data.quantity,
284 }));
285 setSalesTrend(trends);
286 }
287
288 // Load brand data
289 const brandsResult = await apiClient.getCustomerStats({
290 customerId: Number(customerId),
291 type: 'brands',
292 });
293
294 console.log('Brands data:', brandsResult);
295
296 } catch (error) {
297 console.error("Error loading analytics:", error);
298 }
299 };
300
301 const loadContactLogs = async () => {
302 try {
303 const result = await apiClient.getContactLogs({
304 customerId: Number(customerId),
305 });
306
307 if (result.success && result.data) {
308 const logs: ContactLog[] = result.data.map((item: any) => ({
309 date: item.f110 || "",
310 message: item.f120 || "",
311 }));
312 setContactLogs(logs);
313 }
314 } catch (error) {
315 console.error("Error loading contact logs:", error);
316 }
317 };
318
319 const saveContactLog = async () => {
320 if (!newLogEntry.trim()) return;
321
322 try {
323 await apiClient.saveContactLog({
324 customerId: Number(customerId),
325 message: newLogEntry,
326 reminder: logReminder,
327 });
328
329 setNewLogEntry("");
330 setLogReminder("");
331 loadContactLogs();
332 } catch (error) {
333 console.error("Error saving contact log:", error);
334 alert("Failed to save contact log");
335 }
336 };
337
338 const handleEdit = () => {
339 setIsEditing(true);
340 };
341
342 const handleCancelEdit = () => {
343 if (customer) {
344 setEditForm({
345 name: customer.name,
346 company: customer.company,
347 phone: customer.phone,
348 phone2: customer.phone2,
349 mobile: customer.mobile,
350 email: customer.email,
351 division: customer.division,
352 });
353 }
354 setIsEditing(false);
355 };
356
357 const handleSaveEdit = async () => {
358 try {
359 setSaveMessage(null);
360 const result = await apiClient.updateCustomer(Number(customerId), editForm);
361
362 if (result.success) {
363 // Update local customer state
364 setCustomer({
365 ...customer!,
366 name: editForm.name,
367 company: editForm.company,
368 phone: editForm.phone,
369 phone2: editForm.phone2,
370 mobile: editForm.mobile,
371 email: editForm.email,
372 division: editForm.division,
373 });
374 setIsEditing(false);
375 setSaveMessage({ type: "success", text: "Customer updated successfully" });
376
377 // Clear success message after 3 seconds
378 setTimeout(() => setSaveMessage(null), 3000);
379 } else {
380 setSaveMessage({
381 type: "error",
382 text: "Failed to update customer: " + (result.error || "Unknown error"),
383 });
384 }
385 } catch (error) {
386 console.error("Error saving customer:", error);
387 setSaveMessage({
388 type: "error",
389 text: "Failed to save customer changes",
390 });
391 }
392 };
393
394 const formatCurrency = (value: number) => {
395 return new Intl.NumberFormat("en-US", {
396 style: "currency",
397 currency: "USD",
398 minimumFractionDigits: 2,
399 }).format(value);
400 };
401
402 const formatDate = (dateStr: string) => {
403 if (!dateStr) return "";
404 const date = new Date(dateStr);
405 return date.toLocaleDateString("en-GB", {
406 day: "2-digit",
407 month: "short",
408 year: "numeric",
409 });
410 };
411
412 if (loading) {
413 return (
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>
418 </div>
419 </div>
420 );
421 }
422
423 if (!customer) {
424 return (
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.
430 </p>
431 </div>
432 </div>
433 );
434 }
435
436 return (
437 <div className="p-6 bg-surface-2 min-h-screen">
438 {/* Header */}
439 <div className="mb-6 flex items-center justify-between">
440 <div>
441 <div className="flex items-center gap-4">
442 <a
443 href="/pages/customers"
444 className="text-[#00543b] hover:underline"
445 >
446 ← Back to Customers
447 </a>
448 </div>
449 <h1 className="text-3xl font-bold text-text mt-2">
450 {customer.name}
451 </h1>
452 {customer.company && (
453 <p className="text-muted text-lg">{customer.company}</p>
454 )}
455 </div>
456 <div className="flex gap-3 items-center">
457 {saveMessage && (
458 <div
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"
463 }`}
464 >
465 {saveMessage.text}
466 </div>
467 )}
468 {isEditing ? (
469 <>
470 <button
471 onClick={handleCancelEdit}
472 className="bg-muted text-white px-6 py-3 rounded-lg hover:bg-muted transition-colors"
473 >
474 Cancel
475 </button>
476 <button
477 onClick={handleSaveEdit}
478 className="bg-[#00543b] text-white px-6 py-3 rounded-lg hover:bg-[#003d2b] transition-colors"
479 >
480 Save Changes
481 </button>
482 </>
483 ) : (
484 <button
485 onClick={handleEdit}
486 className="bg-[#00543b] text-white px-6 py-3 rounded-lg hover:bg-[#003d2b] transition-colors"
487 >
488 Edit Customer
489 </button>
490 )}
491 </div>
492 </div>
493
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">
498 <div>
499 <h2 className="text-sm font-medium text-white/80 mb-1">
500 Linked Customer Account
501 </h2>
502 <div className="flex items-center gap-3">
503 <a
504 href={`/pages/customers/customer-accounts?account=${customer.accountId}`}
505 className="text-2xl font-bold text-white hover:underline"
506 >
507 {customer.accountName || `Account #${customer.accountId}`}
508 </a>
509 <span className="bg-surface/20 px-3 py-1 rounded-full text-sm">
510 Account ID: {customer.accountId}
511 </span>
512 </div>
513 </div>
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
518 ? 'text-yellow-300'
519 : customer.accountBalance < 0
520 ? 'text-red-300'
521 : 'text-white'
522 }`}>
523 {formatCurrency(customer.accountBalance)}
524 </div>
525 </div>
526 </div>
527 </div>
528 ) : (
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">
532 <svg
533 className="w-6 h-6 text-muted/70"
534 fill="none"
535 stroke="currentColor"
536 viewBox="0 0 24 24"
537 >
538 <path
539 strokeLinecap="round"
540 strokeLinejoin="round"
541 strokeWidth={2}
542 d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
543 />
544 </svg>
545 </div>
546 <div>
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
550 </p>
551 </div>
552 </div>
553 </div>
554 )}
555
556 <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
557 {/* Main Content */}
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">
562 Customer Information
563 </h2>
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">
567 Customer ID
568 </div>
569 <div className="text-text">{customer.id}</div>
570 </div>
571 <div className="col-span-2">
572 <label className="text-sm font-medium text-muted mb-1 block">
573 Name
574 </label>
575 {isEditing ? (
576 <input
577 type="text"
578 value={editForm.name}
579 onChange={(e) =>
580 setEditForm({ ...editForm, name: e.target.value })
581 }
582 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
583 />
584 ) : (
585 <div className="text-text">{customer.name}</div>
586 )}
587 </div>
588 <div className="col-span-2">
589 <label className="text-sm font-medium text-muted mb-1 block">
590 Company Name
591 </label>
592 {isEditing ? (
593 <input
594 type="text"
595 value={editForm.company}
596 onChange={(e) =>
597 setEditForm({ ...editForm, company: e.target.value })
598 }
599 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
600 />
601 ) : (
602 <div className="text-text">{customer.company || "—"}</div>
603 )}
604 </div>
605 <div>
606 <label className="text-sm font-medium text-muted mb-1 block">
607 Phone
608 </label>
609 {isEditing ? (
610 <input
611 type="tel"
612 value={editForm.phone}
613 onChange={(e) =>
614 setEditForm({ ...editForm, phone: e.target.value })
615 }
616 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
617 />
618 ) : (
619 <div className="text-text">{customer.phone || "—"}</div>
620 )}
621 </div>
622 <div>
623 <label className="text-sm font-medium text-muted mb-1 block">
624 Phone 2
625 </label>
626 {isEditing ? (
627 <input
628 type="tel"
629 value={editForm.phone2}
630 onChange={(e) =>
631 setEditForm({ ...editForm, phone2: e.target.value })
632 }
633 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
634 />
635 ) : (
636 <div className="text-text">{customer.phone2 || "—"}</div>
637 )}
638 </div>
639 <div>
640 <label className="text-sm font-medium text-muted mb-1 block">
641 Mobile
642 </label>
643 {isEditing ? (
644 <input
645 type="tel"
646 value={editForm.mobile}
647 onChange={(e) =>
648 setEditForm({ ...editForm, mobile: e.target.value })
649 }
650 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
651 />
652 ) : (
653 <div className="text-text">{customer.mobile || "—"}</div>
654 )}
655 </div>
656 <div className="col-span-2">
657 <label className="text-sm font-medium text-muted mb-1 block">
658 Email
659 </label>
660 {isEditing ? (
661 <input
662 type="email"
663 value={editForm.email}
664 onChange={(e) =>
665 setEditForm({ ...editForm, email: e.target.value })
666 }
667 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
668 />
669 ) : (
670 <div className="text-text">
671 {customer.email ? (
672 <a
673 href={`mailto:${customer.email}`}
674 className="text-[#00543b] hover:underline"
675 >
676 {customer.email}
677 </a>
678 ) : (
679 "—"
680 )}
681 </div>
682 )}
683 </div>
684 <div className="col-span-2">
685 <label className="text-sm font-medium text-muted mb-1 block">
686 Division
687 </label>
688 {isEditing ? (
689 <input
690 type="text"
691 value={editForm.division}
692 onChange={(e) =>
693 setEditForm({ ...editForm, division: e.target.value })
694 }
695 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
696 />
697 ) : (
698 <div className="text-text">{customer.division || "—"}</div>
699 )}
700 </div>
701 </div>
702 </div>
703
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">
710 Lifetime Spend
711 </div>
712 <div className="text-2xl font-bold text-[#00543b]">
713 {formatCurrency(customer.lifetimeSpend)}
714 </div>
715 </div>
716 <div className="bg-surface-2 p-4 rounded-lg">
717 <div className="text-sm font-medium text-muted mb-1">
718 Total Sales
719 </div>
720 <div className="text-2xl font-bold text-text">
721 {customer.totalSales}
722 </div>
723 </div>
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">
727 Turnover Rank
728 </div>
729 <div className="text-2xl font-bold text-text">
730 {customer.rank}
731 </div>
732 </div>
733 )}
734 <div className="bg-surface-2 p-4 rounded-lg">
735 <div className="text-sm font-medium text-muted mb-1">
736 Last Sale
737 </div>
738 <div className="text-lg font-semibold text-text">
739 {formatDate(customer.lastSale)}
740 </div>
741 </div>
742 </div>
743 </div>
744
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">
751 Sales Trend
752 </h2>
753 <div className="h-64">
754 <Line
755 data={{
756 labels: salesTrend.map((t) => t.date),
757 datasets: [
758 {
759 label: "Sales Amount",
760 data: salesTrend.map((t) => t.amount),
761 borderColor: "#00543b",
762 backgroundColor: "rgba(0, 84, 59, 0.1)",
763 fill: true,
764 tension: 0.4,
765 },
766 ],
767 }}
768 options={{
769 responsive: true,
770 maintainAspectRatio: false,
771 plugins: {
772 legend: {
773 display: false,
774 },
775 tooltip: {
776 callbacks: {
777 label: (context) =>
778 `$${context.parsed.y?.toFixed(2) || '0.00'}`,
779 },
780 },
781 },
782 scales: {
783 y: {
784 beginAtZero: true,
785 ticks: {
786 callback: (value) => `$${value}`,
787 },
788 },
789 },
790 }}
791 />
792 </div>
793 </div>
794
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
800 </h2>
801 <div className="h-64">
802 <Doughnut
803 data={{
804 labels: departmentSpend.map((d) => d.department),
805 datasets: [
806 {
807 data: departmentSpend.map((d) => d.amount),
808 backgroundColor: departmentSpend.map(
809 (d) => d.color
810 ),
811 borderWidth: 2,
812 borderColor: "#fff",
813 },
814 ],
815 }}
816 options={{
817 responsive: true,
818 maintainAspectRatio: false,
819 plugins: {
820 legend: {
821 position: "right",
822 labels: {
823 boxWidth: 12,
824 font: {
825 size: 11,
826 },
827 },
828 },
829 tooltip: {
830 callbacks: {
831 label: (context) => {
832 const label = context.label || "";
833 const value = context.parsed;
834 const total = departmentSpend.reduce(
835 (sum, d) => sum + d.amount,
836 0
837 );
838 const percentage = (
839 (value / total) *
840 100
841 ).toFixed(1);
842 return `${label}: $${value.toFixed(2)} (${percentage}%)`;
843 },
844 },
845 },
846 },
847 }}
848 />
849 </div>
850 </div>
851 )}
852 </div>
853 )}
854
855 {/* Top Products */}
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
860 </h2>
861 <div className="space-y-3">
862 {topProducts.slice(0, 5).map((product, index) => (
863 <div
864 key={index}
865 className="flex items-center justify-between p-3 bg-surface-2 rounded-lg hover:bg-surface-2 transition-colors"
866 >
867 <div className="flex-1">
868 <div className="font-medium text-text">
869 {product.name}
870 </div>
871 <div className="text-sm text-muted">
872 Quantity: {product.quantity}
873 </div>
874 </div>
875 <div className="text-right">
876 <div className="font-semibold text-[#00543b]">
877 {formatCurrency(product.value)}
878 </div>
879 <div className="text-xs text-muted">
880 {((product.value / customer.lifetimeSpend) * 100).toFixed(1)}% of total
881 </div>
882 </div>
883 </div>
884 ))}
885 </div>
886 </div>
887 )}
888
889 {/* Recent Sales */}
890 <div className="bg-surface rounded-lg shadow p-6">
891 <h2 className="text-xl font-bold text-text mb-4">
892 Recent Purchases
893 </h2>
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">
898 <tr>
899 <th className="text-left py-3 px-4 font-semibold text-sm text-text">
900 Date
901 </th>
902 <th className="text-left py-3 px-4 font-semibold text-sm text-text">
903 Sale #
904 </th>
905 <th className="text-left py-3 px-4 font-semibold text-sm text-text">
906 Description
907 </th>
908 <th className="text-right py-3 px-4 font-semibold text-sm text-text">
909 Qty
910 </th>
911 <th className="text-right py-3 px-4 font-semibold text-sm text-text">
912 Amount
913 </th>
914 </tr>
915 </thead>
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)}
921 </td>
922 <td className="py-3 px-4 text-sm">
923 <a
924 href={`/report/pos/sales/fieldpine/singlesale.htm?sid=${sale.id}`}
925 className="text-[#00543b] hover:underline"
926 >
927 {sale.id}
928 </a>
929 </td>
930 <td className="py-3 px-4 text-sm text-text">
931 {sale.description}
932 </td>
933 <td className="py-3 px-4 text-sm text-text text-right">
934 {sale.qty}
935 </td>
936 <td className="py-3 px-4 text-sm text-text text-right">
937 {formatCurrency(sale.linePrice)}
938 </td>
939 </tr>
940 ))}
941 </tbody>
942 </table>
943 </div>
944 ) : (
945 <div className="text-center py-8 text-muted">
946 No recent sales found
947 </div>
948 )}
949 </div>
950 </div>
951
952 {/* Sidebar */}
953 <div className="space-y-6">
954 {/* Contact Log */}
955 <div className="bg-surface rounded-lg shadow p-6">
956 <h2 className="text-xl font-bold text-text mb-4">
957 Contact Log
958 </h2>
959 <div className="space-y-4">
960 <textarea
961 value={newLogEntry}
962 onChange={(e) => setNewLogEntry(e.target.value)}
963 rows={4}
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..."
966 />
967 <div className="flex items-center gap-2">
968 <input
969 type="datetime-local"
970 value={logReminder}
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"
974 />
975 <button
976 onClick={saveContactLog}
977 className="bg-[#00543b] text-white px-4 py-2 rounded-lg hover:bg-[#003d2b] transition-colors"
978 >
979 Save
980 </button>
981 </div>
982 </div>
983
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) => (
987 <div
988 key={index}
989 className="border-l-4 border-[#00543b] pl-3 py-2 bg-surface-2"
990 >
991 <div className="text-xs text-muted mb-1">
992 {formatDate(log.date)}
993 </div>
994 <div className="text-sm text-text">{log.message}</div>
995 </div>
996 ))}
997 {contactLogs.length === 0 && (
998 <div className="text-center text-muted text-sm py-4">
999 No contact logs yet
1000 </div>
1001 )}
1002 </div>
1003 </div>
1004
1005 {/* Quick Actions */}
1006 <div className="bg-surface rounded-lg shadow p-6">
1007 <h2 className="text-xl font-bold text-text mb-4">
1008 Quick Actions
1009 </h2>
1010 <div className="space-y-2">
1011 <button
1012 onClick={() => {
1013 window.location.href = `/pages/customers/${customerId}`;
1014 }}
1015 className="block w-full text-center bg-surface-2 hover:bg-surface-2 text-text px-4 py-2 rounded-lg transition-colors"
1016 >
1017 Edit Details
1018 </button>
1019 {customer.email && (
1020 <a
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"
1023 >
1024 Send Email
1025 </a>
1026 )}
1027 <a
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"
1030 >
1031 View Reports
1032 </a>
1033 </div>
1034 </div>
1035 </div>
1036 </div>
1037 </div>
1038 );
1039}