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, useRouter } 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 SupplierData {
35 id: number;
36 name: string;
37 phone: string;
38 fax: string;
39 email: string;
40 address1: string;
41 address2: string;
42 city: string;
43 state: string;
44 postalCode: string;
45 country: string;
46 key: string;
47 accountId?: number;
48 accountBalance?: number;
49 totalOrders?: number;
50 totalSpend?: number;
51 firstOrder?: string;
52 lastOrder?: string;
53}
54
55interface PurchaseOrder {
56 id: number;
57 date: string;
58 status: string;
59 total: number;
60 items: number;
61 expectedDate?: string;
62 receivedDate?: string;
63 store: string;
64}
65
66interface TopProduct {
67 name: string;
68 quantity: number;
69 value: number;
70 lastOrdered?: string;
71}
72
73interface ContactLog {
74 date: string;
75 message: string;
76}
77
78interface DepartmentSpend {
79 department: string;
80 amount: number;
81 color: string;
82}
83
84interface OrderTrend {
85 date: string;
86 amount: number;
87 quantity: number;
88}
89
90interface Contact {
91 id: string;
92 name: string;
93 firstName: string;
94 lastName: string;
95 title: string;
96 phone: string;
97 mobile: string;
98 email: string;
99 comments: string;
100}
101
102interface StoreCode {
103 storeId: number;
104 storeName: string;
105 code: string;
106}
107
108interface FreightConfig {
109 allFree: boolean;
110 fixedCost: string;
111 value1High: string;
112 value1Charge: string;
113 value2Low: string;
114 value2High: string;
115 value2Charge: string;
116 aboveFree: string;
117 comments: string;
118}
119
120interface OrderSettings {
121 email: string;
122 emailSubject: string;
123 emailNormal: boolean;
124 emailPDF: boolean;
125 format: string;
126 minOrderValue: string;
127 maxOrderValue: string;
128 minRetailValue: string;
129 maxRetailValue: string;
130 minUnits: string;
131 maxUnits: string;
132 minLines: string;
133 maxLines: string;
134 orderMessage: string;
135 messageStartDate: string;
136 messageEndDate: string;
137}
138
139export default function SupplierProfilePage() {
140 const params = useParams();
141 const router = useRouter();
142 const supplierId = params.id as string;
143
144 const [activeTab, setActiveTab] = useState<string>("overview");
145 const [supplier, setSupplier] = useState<SupplierData | null>(null);
146 const [recentOrders, setRecentOrders] = useState<PurchaseOrder[]>([]);
147 const [topProducts, setTopProducts] = useState<TopProduct[]>([]);
148 const [contactLogs, setContactLogs] = useState<ContactLog[]>([]);
149 const [departmentSpend, setDepartmentSpend] = useState<DepartmentSpend[]>([]);
150 const [orderTrend, setOrderTrend] = useState<OrderTrend[]>([]);
151 const [contacts, setContacts] = useState<Contact[]>([]);
152 const [storeCodes, setStoreCodes] = useState<StoreCode[]>([]);
153 const [freightConfig, setFreightConfig] = useState<FreightConfig>({
154 allFree: false,
155 fixedCost: "",
156 value1High: "",
157 value1Charge: "",
158 value2Low: "",
159 value2High: "",
160 value2Charge: "",
161 aboveFree: "",
162 comments: "",
163 });
164 const [orderSettings, setOrderSettings] = useState<OrderSettings>({
165 email: "",
166 emailSubject: "",
167 emailNormal: true,
168 emailPDF: false,
169 format: "401",
170 minOrderValue: "",
171 maxOrderValue: "",
172 minRetailValue: "",
173 maxRetailValue: "",
174 minUnits: "",
175 maxUnits: "",
176 minLines: "",
177 maxLines: "",
178 orderMessage: "",
179 messageStartDate: "",
180 messageEndDate: "",
181 });
182 const [showContactForm, setShowContactForm] = useState(false);
183 const [editingContact, setEditingContact] = useState<Contact | null>(null);
184 const [contactForm, setContactForm] = useState({
185 name: "",
186 firstName: "",
187 lastName: "",
188 title: "",
189 phone: "",
190 mobile: "",
191 email: "",
192 comments: "",
193 });
194 const [loading, setLoading] = useState(true);
195 const [newLogEntry, setNewLogEntry] = useState("");
196 const [logReminder, setLogReminder] = useState("");
197 const [isEditing, setIsEditing] = useState(false);
198 const [editForm, setEditForm] = useState({
199 name: "",
200 phone: "",
201 fax: "",
202 email: "",
203 address1: "",
204 address2: "",
205 city: "",
206 state: "",
207 postalCode: "",
208 country: "",
209 });
210 const [saveMessage, setSaveMessage] = useState<{
211 type: "success" | "error";
212 text: string;
213 } | null>(null);
214
215 useEffect(() => {
216 if (supplierId) {
217 console.log('Loading supplier with ID:', supplierId);
218 loadSupplierData();
219 loadRecentOrders();
220 loadContactLogs();
221 loadAnalytics();
222 loadContacts();
223 loadStoreCodes();
224 loadFreightConfig();
225 loadOrderSettings();
226 }
227 }, [supplierId]);
228
229 const loadSupplierData = async () => {
230 try {
231 setLoading(true);
232 console.log('Fetching supplier data for ID:', supplierId);
233
234 const result = await apiClient.getSuppliers({
235 search: supplierId,
236 source: 'openapi',
237 });
238
239 console.log('Supplier API result:', result);
240
241 if (result.success && result.data) {
242 const apiData = result.data as any;
243 const supplierData = apiData?.data?.DATS || [];
244
245 // Find supplier by ID
246 const data = supplierData.find((s: any) => s.Spid === Number(supplierId));
247
248 if (data) {
249 console.log('Supplier data:', data);
250
251 setSupplier({
252 id: data.Spid || 0,
253 name: data.Name || "",
254 phone: data.Phone || "",
255 fax: data.Fax || "",
256 email: data.Email || "",
257 address1: data.Address1 || "",
258 address2: data.Address2 || "",
259 city: data.City || "",
260 state: data.State || "",
261 postalCode: data.PostalCode || "",
262 country: data.Country || "",
263 key: data.Key || "",
264 accountId: 0,
265 accountBalance: 0,
266 totalOrders: 0,
267 totalSpend: 0,
268 firstOrder: "",
269 lastOrder: "",
270 });
271
272 // Initialize edit form
273 setEditForm({
274 name: data.Name || "",
275 phone: data.Phone || "",
276 fax: data.Fax || "",
277 email: data.Email || "",
278 address1: data.Address1 || "",
279 address2: data.Address2 || "",
280 city: data.City || "",
281 state: data.State || "",
282 postalCode: data.PostalCode || "",
283 country: data.Country || "",
284 });
285 } else {
286 console.error('Supplier not found');
287 }
288 } else {
289 console.error('No supplier data found in response');
290 }
291 setLoading(false);
292 } catch (error) {
293 console.error("Error loading supplier data:", error);
294 setLoading(false);
295 }
296 };
297
298 const loadRecentOrders = async () => {
299 try {
300 const result = await apiClient.getPurchaseOrders({ spid: parseInt(supplierId) });
301
302 if (result.success && result.data) {
303 // Map OpenAPI PurchaseOrder data to our interface
304 const orders = result.data.map((po: any) => {
305 // Count items in the ITEM array
306 const itemCount = po.ITEM ? (Array.isArray(po.ITEM) ? po.ITEM.length : 1) : 0;
307
308 // Determine status based on PoState
309 let status = "Unknown";
310 if (po.PoState === "1" || po.PoState === 1) status = "Pending";
311 else if (po.PoState === "2" || po.PoState === 2) status = "Received";
312 else if (po.PoState === "3" || po.PoState === 3) status = "Partial";
313 else if (po.PoState === "4" || po.PoState === 4) status = "Cancelled";
314
315 return {
316 id: parseInt(po.Poid) || 0,
317 date: po.EntryDt ? new Date(po.EntryDt).toLocaleDateString() : "N/A",
318 status: status,
319 total: parseFloat(po.TotalExTax) || 0,
320 items: itemCount,
321 expectedDate: po.ExpectedDt ? new Date(po.ExpectedDt).toLocaleDateString() : undefined,
322 store: po.ShipToLoc ? `Location ${po.ShipToLoc}` : "N/A"
323 };
324 });
325
326 // Sort by date (most recent first) and take top 5
327 const sortedOrders = orders.sort((a, b) =>
328 new Date(b.date).getTime() - new Date(a.date).getTime()
329 ).slice(0, 5);
330
331 setRecentOrders(sortedOrders);
332 } else {
333 // If no data, set empty array
334 setRecentOrders([]);
335 }
336 } catch (error) {
337 console.error("Error loading orders:", error);
338 setRecentOrders([]);
339 }
340 };
341
342 const loadAnalytics = async () => {
343 try {
344 // Mock analytics data
345 const mockProducts: TopProduct[] = [
346 { name: "Product A", quantity: 500, value: 12500, lastOrdered: "2024-12-20" },
347 { name: "Product B", quantity: 350, value: 8750, lastOrdered: "2024-12-18" },
348 { name: "Product C", quantity: 280, value: 7000, lastOrdered: "2024-12-15" },
349 ];
350 setTopProducts(mockProducts);
351
352 const mockDeptSpend: DepartmentSpend[] = [
353 { department: "Electronics", amount: 25000, color: "#00543b" },
354 { department: "Hardware", amount: 18000, color: "#007d57" },
355 { department: "Garden", amount: 12000, color: "#00a874" },
356 ];
357 setDepartmentSpend(mockDeptSpend);
358
359 const mockTrend: OrderTrend[] = [
360 { date: "Nov", amount: 15000, quantity: 450 },
361 { date: "Dec", amount: 22000, quantity: 680 },
362 ];
363 setOrderTrend(mockTrend);
364
365 } catch (error) {
366 console.error("Error loading analytics:", error);
367 }
368 };
369
370 const loadContacts = async () => {
371 try {
372 // Mock contacts - would come from supplier contacts API
373 const mockContacts: Contact[] = [
374 {
375 id: "1",
376 name: "John Smith",
377 firstName: "John",
378 lastName: "Smith",
379 title: "Sales Manager",
380 phone: "(555) 123-4567",
381 mobile: "(555) 987-6543",
382 email: "john.smith@supplier.com",
383 comments: "Primary contact for orders"
384 },
385 {
386 id: "2",
387 name: "Sarah Johnson",
388 firstName: "Sarah",
389 lastName: "Johnson",
390 title: "Account Manager",
391 phone: "(555) 234-5678",
392 mobile: "",
393 email: "sarah.j@supplier.com",
394 comments: "Handles invoicing queries"
395 }
396 ];
397 setContacts(mockContacts);
398 } catch (error) {
399 console.error("Error loading contacts:", error);
400 }
401 };
402
403 const loadStoreCodes = async () => {
404 try {
405 // Mock store codes - would come from locations API with supplier codes
406 const mockCodes: StoreCode[] = [
407 { storeId: 1, storeName: "Main Store", code: "MS-001" },
408 { storeId: 2, storeName: "North Branch", code: "NB-002" },
409 { storeId: 3, storeName: "South Branch", code: "SB-003" },
410 ];
411 setStoreCodes(mockCodes);
412 } catch (error) {
413 console.error("Error loading store codes:", error);
414 }
415 };
416
417 const loadFreightConfig = async () => {
418 try {
419 // Mock freight config - would come from supplier freight settings API
420 setFreightConfig({
421 allFree: false,
422 fixedCost: "",
423 value1High: "500.00",
424 value1Charge: "25.00",
425 value2Low: "500.00",
426 value2High: "1000.00",
427 value2Charge: "15.00",
428 aboveFree: "1000.00",
429 comments: "Free shipping on orders over $1000"
430 });
431 } catch (error) {
432 console.error("Error loading freight config:", error);
433 }
434 };
435
436 const loadOrderSettings = async () => {
437 try {
438 // Mock order settings - would come from supplier order settings API
439 setOrderSettings({
440 email: "orders@supplier.com",
441 emailSubject: "Purchase Order from EverydayPOS",
442 emailNormal: true,
443 emailPDF: true,
444 format: "401",
445 minOrderValue: "100.00",
446 maxOrderValue: "",
447 minRetailValue: "",
448 maxRetailValue: "",
449 minUnits: "10",
450 maxUnits: "",
451 minLines: "",
452 maxLines: "",
453 orderMessage: "",
454 messageStartDate: "",
455 messageEndDate: "",
456 });
457 } catch (error) {
458 console.error("Error loading order settings:", error);
459 }
460 };
461
462 const loadContactLogs = async () => {
463 try {
464 // Mock contact logs
465 const mockLogs: ContactLog[] = [
466 { date: "2024-12-20", message: "Discussed new product line for Q1 2025" },
467 { date: "2024-12-15", message: "Confirmed delivery schedule for next order" },
468 ];
469 setContactLogs(mockLogs);
470 } catch (error) {
471 console.error("Error loading contact logs:", error);
472 }
473 };
474
475 const handleSaveContactLog = () => {
476 if (newLogEntry.trim()) {
477 const newLog: ContactLog = {
478 date: new Date().toISOString().split("T")[0],
479 message: newLogEntry.trim(),
480 };
481 setContactLogs([newLog, ...contactLogs]);
482 setNewLogEntry("");
483
484 // TODO: Save to API
485 console.log("Saving contact log:", newLog);
486 }
487 };
488
489 const handleSaveEdit = async () => {
490 try {
491 // TODO: Call API to update supplier
492 console.log("Saving supplier data:", editForm);
493
494 // Update local state
495 if (supplier) {
496 setSupplier({
497 ...supplier,
498 ...editForm,
499 });
500 }
501
502 setSaveMessage({ type: "success", text: "Supplier updated successfully!" });
503 setIsEditing(false);
504
505 setTimeout(() => setSaveMessage(null), 3000);
506 } catch (error) {
507 console.error("Error saving supplier:", error);
508 setSaveMessage({ type: "error", text: "Failed to update supplier" });
509 setTimeout(() => setSaveMessage(null), 3000);
510 }
511 };
512
513 // Contact Management Handlers
514 const handleSaveContact = () => {
515 if (editingContact) {
516 // Update existing contact
517 setContacts(contacts.map(c =>
518 c.id === editingContact.id
519 ? { ...c, ...contactForm }
520 : c
521 ));
522 setSaveMessage({ type: "success", text: "Contact updated successfully!" });
523 } else {
524 // Add new contact
525 const newContact: Contact = {
526 id: Date.now().toString(),
527 ...contactForm,
528 };
529 setContacts([...contacts, newContact]);
530 setSaveMessage({ type: "success", text: "Contact added successfully!" });
531 }
532
533 setShowContactForm(false);
534 setEditingContact(null);
535 setContactForm({
536 name: "",
537 firstName: "",
538 lastName: "",
539 title: "",
540 phone: "",
541 mobile: "",
542 email: "",
543 comments: "",
544 });
545 setTimeout(() => setSaveMessage(null), 3000);
546 };
547
548 const handleEditContact = (contact: Contact) => {
549 setEditingContact(contact);
550 setContactForm({
551 name: contact.name,
552 firstName: contact.firstName,
553 lastName: contact.lastName,
554 title: contact.title,
555 phone: contact.phone,
556 mobile: contact.mobile,
557 email: contact.email,
558 comments: contact.comments,
559 });
560 setShowContactForm(true);
561 };
562
563 const handleDeleteContact = (contactId: string) => {
564 if (confirm("Are you sure you want to delete this contact?")) {
565 setContacts(contacts.filter(c => c.id !== contactId));
566 setSaveMessage({ type: "success", text: "Contact deleted successfully!" });
567 setTimeout(() => setSaveMessage(null), 3000);
568 }
569 };
570
571 // Store Code Handlers
572 const handleUpdateStoreCode = (storeId: number, code: string) => {
573 setStoreCodes(storeCodes.map(sc =>
574 sc.storeId === storeId ? { ...sc, code } : sc
575 ));
576 // TODO: Save to API
577 console.log("Updating store code:", storeId, code);
578 };
579
580 // Freight Config Handlers
581 const handleUpdateFreightConfig = (field: keyof FreightConfig, value: string | boolean) => {
582 setFreightConfig({ ...freightConfig, [field]: value });
583 // TODO: Save to API
584 console.log("Updating freight config:", field, value);
585 };
586
587 // Order Settings Handlers
588 const handleUpdateOrderSettings = (field: keyof OrderSettings, value: string | boolean) => {
589 setOrderSettings({ ...orderSettings, [field]: value });
590 // TODO: Save to API
591 console.log("Updating order settings:", field, value);
592 };
593
594 const handleCancelEdit = () => {
595 if (supplier) {
596 setEditForm({
597 name: supplier.name,
598 phone: supplier.phone,
599 fax: supplier.fax,
600 email: supplier.email,
601 address1: supplier.address1,
602 address2: supplier.address2,
603 city: supplier.city,
604 state: supplier.state,
605 postalCode: supplier.postalCode,
606 country: supplier.country,
607 });
608 }
609 setIsEditing(false);
610 };
611
612 // Chart configurations
613 const orderTrendData = {
614 labels: orderTrend.map((item) => item.date),
615 datasets: [
616 {
617 label: "Order Value ($)",
618 data: orderTrend.map((item) => item.amount),
619 borderColor: "#00946b",
620 backgroundColor: "rgba(0, 148, 107, 0.1)",
621 fill: true,
622 tension: 0.4,
623 },
624 ],
625 };
626
627 const departmentData = {
628 labels: departmentSpend.map((item) => item.department),
629 datasets: [
630 {
631 data: departmentSpend.map((item) => item.amount),
632 backgroundColor: departmentSpend.map((item) => item.color),
633 borderWidth: 0,
634 },
635 ],
636 };
637
638 const chartOptions = {
639 responsive: true,
640 maintainAspectRatio: false,
641 plugins: {
642 legend: {
643 display: false,
644 },
645 },
646 };
647
648 if (loading) {
649 return (
650 <div className="p-6 min-h-screen bg-bg">
651 <div className="flex items-center justify-center h-64">
652 <div className="text-center">
653 <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-[#00946b] mx-auto"></div>
654 <p className="mt-4 text-muted">Loading supplier data...</p>
655 </div>
656 </div>
657 </div>
658 );
659 }
660
661 if (!supplier) {
662 return (
663 <div className="p-6 min-h-screen bg-bg">
664 <div className="max-w-7xl mx-auto">
665 <div className="bg-surface rounded-lg shadow-md p-8 text-center">
666 <h2 className="text-2xl font-bold text-text mb-4">Supplier Not Found</h2>
667 <p className="text-muted mb-6">The supplier with ID {supplierId} could not be found.</p>
668 <button
669 onClick={() => router.push("/pages/suppliers")}
670 className="px-6 py-2 bg-[#00946b] text-white rounded-lg hover:bg-[#00543b] transition-colors"
671 >
672 Back to Suppliers
673 </button>
674 </div>
675 </div>
676 </div>
677 );
678 }
679
680 return (
681 <div className="p-6 min-h-screen bg-bg">
682 <div className="max-w-7xl mx-auto">
683 {/* Header */}
684 <div className="mb-6 flex items-center justify-between">
685 <div>
686 <button
687 onClick={() => router.push("/pages/suppliers")}
688 className="text-[#00946b] hover:text-[#00543b] mb-2 flex items-center gap-2"
689 >
690 <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
691 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
692 </svg>
693 Back to Suppliers
694 </button>
695 <h1 className="text-3xl font-bold text-text">{supplier.name}</h1>
696 <p className="text-muted">Supplier ID: {supplier.id}</p>
697 </div>
698 <button
699 onClick={() => setIsEditing(!isEditing)}
700 className="px-4 py-2 bg-[#00946b] text-white rounded-lg hover:bg-[#00543b] transition-colors"
701 >
702 {isEditing ? "Cancel" : "Edit Supplier"}
703 </button>
704 </div>
705
706 {/* Save Message */}
707 {saveMessage && (
708 <div
709 className={`mb-4 p-4 rounded-lg ${
710 saveMessage.type === "success"
711 ? "bg-green-50 text-green-800 border border-green-200"
712 : "bg-red-50 text-red-800 border border-red-200"
713 }`}
714 >
715 {saveMessage.text}
716 </div>
717 )}
718
719 {/* Tabs */}
720 <div className="mb-6 bg-surface rounded-lg shadow-sm border border-border p-1">
721 <nav className="flex gap-2">
722 <button
723 onClick={() => setActiveTab("overview")}
724 className={`flex-1 py-3 px-6 font-semibold text-base rounded-md transition-all ${
725 activeTab === "overview"
726 ? "bg-[#00543b] text-white shadow-md"
727 : "bg-transparent text-muted hover:bg-surface-2 hover:text-text"
728 }`}
729 >
730 <span className="text-xl mr-2">📊</span>
731 Overview
732 </button>
733 <button
734 onClick={() => setActiveTab("contacts")}
735 className={`flex-1 py-3 px-6 font-semibold text-base rounded-md transition-all ${
736 activeTab === "contacts"
737 ? "bg-[#00543b] text-white shadow-md"
738 : "bg-transparent text-muted hover:bg-surface-2 hover:text-text"
739 }`}
740 >
741 <span className="text-xl mr-2">👥</span>
742 Contacts
743 <span className={`ml-2 px-2 py-0.5 text-xs rounded-full ${
744 activeTab === "contacts"
745 ? "bg-surface/20 text-white"
746 : "bg-surface-2 text-text"
747 }`}>
748 {contacts.length}
749 </span>
750 </button>
751 <button
752 onClick={() => setActiveTab("ordering")}
753 className={`flex-1 py-3 px-6 font-semibold text-base rounded-md transition-all ${
754 activeTab === "ordering"
755 ? "bg-[#00543b] text-white shadow-md"
756 : "bg-transparent text-muted hover:bg-surface-2 hover:text-text"
757 }`}
758 >
759 <span className="text-xl mr-2">📦</span>
760 Ordering
761 </button>
762 <button
763 onClick={() => setActiveTab("stores")}
764 className={`flex-1 py-3 px-6 font-semibold text-base rounded-md transition-all ${
765 activeTab === "stores"
766 ? "bg-[#00543b] text-white shadow-md"
767 : "bg-transparent text-muted hover:bg-surface-2 hover:text-text"
768 }`}
769 >
770 <span className="text-xl mr-2">🏪</span>
771 Store Codes
772 <span className={`ml-2 px-2 py-0.5 text-xs rounded-full ${
773 activeTab === "stores"
774 ? "bg-surface/20 text-white"
775 : "bg-surface-2 text-text"
776 }`}>
777 {storeCodes.length}
778 </span>
779 </button>
780 </nav>
781 </div>
782
783 {/* Overview Tab */}
784 {activeTab === "overview" && (
785 <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
786 {/* Left Column - Supplier Info */}
787 <div className="lg:col-span-1 space-y-6">
788 {/* Contact Information */}
789 <div className="bg-surface rounded-lg shadow-md p-6">
790 <h2 className="text-xl font-bold text-text mb-4">Contact Information</h2>
791
792 {isEditing ? (
793 <div className="space-y-4">
794 <div>
795 <label className="block text-sm font-medium text-text mb-1">Name</label>
796 <input
797 type="text"
798 value={editForm.name}
799 onChange={(e) => setEditForm({ ...editForm, name: e.target.value })}
800 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
801 />
802 </div>
803 <div>
804 <label className="block text-sm font-medium text-text mb-1">Email</label>
805 <input
806 type="email"
807 value={editForm.email}
808 onChange={(e) => setEditForm({ ...editForm, email: e.target.value })}
809 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
810 />
811 </div>
812 <div>
813 <label className="block text-sm font-medium text-text mb-1">Phone</label>
814 <input
815 type="tel"
816 value={editForm.phone}
817 onChange={(e) => setEditForm({ ...editForm, phone: e.target.value })}
818 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
819 />
820 </div>
821 <div>
822 <label className="block text-sm font-medium text-text mb-1">Fax</label>
823 <input
824 type="tel"
825 value={editForm.fax}
826 onChange={(e) => setEditForm({ ...editForm, fax: e.target.value })}
827 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
828 />
829 </div>
830 <div>
831 <label className="block text-sm font-medium text-text mb-1">Address Line 1</label>
832 <input
833 type="text"
834 value={editForm.address1}
835 onChange={(e) => setEditForm({ ...editForm, address1: e.target.value })}
836 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
837 />
838 </div>
839 <div>
840 <label className="block text-sm font-medium text-text mb-1">Address Line 2</label>
841 <input
842 type="text"
843 value={editForm.address2}
844 onChange={(e) => setEditForm({ ...editForm, address2: e.target.value })}
845 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
846 />
847 </div>
848 <div className="grid grid-cols-2 gap-3">
849 <div>
850 <label className="block text-sm font-medium text-text mb-1">City</label>
851 <input
852 type="text"
853 value={editForm.city}
854 onChange={(e) => setEditForm({ ...editForm, city: e.target.value })}
855 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
856 />
857 </div>
858 <div>
859 <label className="block text-sm font-medium text-text mb-1">State</label>
860 <input
861 type="text"
862 value={editForm.state}
863 onChange={(e) => setEditForm({ ...editForm, state: e.target.value })}
864 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
865 />
866 </div>
867 </div>
868 <div className="grid grid-cols-2 gap-3">
869 <div>
870 <label className="block text-sm font-medium text-text mb-1">Postal Code</label>
871 <input
872 type="text"
873 value={editForm.postalCode}
874 onChange={(e) => setEditForm({ ...editForm, postalCode: e.target.value })}
875 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
876 />
877 </div>
878 <div>
879 <label className="block text-sm font-medium text-text mb-1">Country</label>
880 <input
881 type="text"
882 value={editForm.country}
883 onChange={(e) => setEditForm({ ...editForm, country: e.target.value })}
884 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
885 />
886 </div>
887 </div>
888
889 <div className="flex gap-3 pt-4">
890 <button
891 onClick={handleSaveEdit}
892 className="flex-1 px-4 py-2 bg-[#00946b] text-white rounded-lg hover:bg-[#00543b] transition-colors"
893 >
894 Save Changes
895 </button>
896 <button
897 onClick={handleCancelEdit}
898 className="flex-1 px-4 py-2 bg-surface-2 text-text rounded-lg hover:bg-surface-2 transition-colors"
899 >
900 Cancel
901 </button>
902 </div>
903 </div>
904 ) : (
905 <div className="space-y-3">
906 {supplier.email && (
907 <div className="flex items-start gap-3">
908 <svg className="w-5 h-5 text-muted/70 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
909 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
910 </svg>
911 <div>
912 <p className="text-sm text-muted">Email</p>
913 <a href={`mailto:${supplier.email}`} className="text-[#00946b] hover:underline">
914 {supplier.email}
915 </a>
916 </div>
917 </div>
918 )}
919
920 {supplier.phone && (
921 <div className="flex items-start gap-3">
922 <svg className="w-5 h-5 text-muted/70 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
923 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
924 </svg>
925 <div>
926 <p className="text-sm text-muted">Phone</p>
927 <a href={`tel:${supplier.phone}`} className="text-[#00946b] hover:underline">
928 {supplier.phone}
929 </a>
930 </div>
931 </div>
932 )}
933
934 {supplier.fax && (
935 <div className="flex items-start gap-3">
936 <svg className="w-5 h-5 text-muted/70 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
937 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 4v16M17 4v16M3 8h4m10 0h4M3 12h18M3 16h4m10 0h4M4 20h16a1 1 0 001-1V5a1 1 0 00-1-1H4a1 1 0 00-1 1v14a1 1 0 001 1z" />
938 </svg>
939 <div>
940 <p className="text-sm text-muted">Fax</p>
941 <p className="text-text">{supplier.fax}</p>
942 </div>
943 </div>
944 )}
945
946 {(supplier.address1 || supplier.city) && (
947 <div className="flex items-start gap-3">
948 <svg className="w-5 h-5 text-muted/70 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
949 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
950 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
951 </svg>
952 <div>
953 <p className="text-sm text-muted">Address</p>
954 <div className="text-text">
955 {supplier.address1 && <p>{supplier.address1}</p>}
956 {supplier.address2 && <p>{supplier.address2}</p>}
957 {(supplier.city || supplier.state || supplier.postalCode) && (
958 <p>
959 {[supplier.city, supplier.state, supplier.postalCode]
960 .filter(Boolean)
961 .join(", ")}
962 </p>
963 )}
964 {supplier.country && <p>{supplier.country}</p>}
965 </div>
966 </div>
967 </div>
968 )}
969 </div>
970 )}
971 </div>
972
973 {/* Quick Stats */}
974 <div className="bg-surface rounded-lg shadow-md p-6">
975 <h2 className="text-xl font-bold text-text mb-4">Quick Stats</h2>
976 <div className="space-y-4">
977 <div className="flex justify-between items-center p-3 bg-green-50 rounded-lg">
978 <span className="text-sm font-medium text-text">Total Orders</span>
979 <span className="text-lg font-bold text-[#00946b]">{supplier.totalOrders || 0}</span>
980 </div>
981 <div className="flex justify-between items-center p-3 bg-info/10 rounded-lg">
982 <span className="text-sm font-medium text-text">Total Spend</span>
983 <span className="text-lg font-bold text-brand">
984 ${(supplier.totalSpend || 0).toLocaleString()}
985 </span>
986 </div>
987 <div className="flex justify-between items-center p-3 bg-yellow-50 rounded-lg">
988 <span className="text-sm font-medium text-text">Account Balance</span>
989 <span className="text-lg font-bold text-yellow-600">
990 ${(supplier.accountBalance || 0).toLocaleString()}
991 </span>
992 </div>
993 </div>
994 </div>
995
996 {/* Contact Log */}
997 <div className="bg-surface rounded-lg shadow-md p-6">
998 <h2 className="text-xl font-bold text-text mb-4">Contact Log</h2>
999
1000 <div className="mb-4">
1001 <textarea
1002 value={newLogEntry}
1003 onChange={(e) => setNewLogEntry(e.target.value)}
1004 placeholder="Add a note about this supplier..."
1005 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent resize-none"
1006 rows={3}
1007 />
1008 <button
1009 onClick={handleSaveContactLog}
1010 className="mt-2 w-full px-4 py-2 bg-[#00946b] text-white rounded-lg hover:bg-[#00543b] transition-colors"
1011 >
1012 Add Log Entry
1013 </button>
1014 </div>
1015
1016 <div className="space-y-3 max-h-64 overflow-y-auto">
1017 {contactLogs.length === 0 ? (
1018 <p className="text-sm text-muted text-center py-4">No contact logs yet</p>
1019 ) : (
1020 contactLogs.map((log, index) => (
1021 <div key={index} className="p-3 bg-surface-2 rounded-lg">
1022 <p className="text-xs text-muted mb-1">{log.date}</p>
1023 <p className="text-sm text-text">{log.message}</p>
1024 </div>
1025 ))
1026 )}
1027 </div>
1028 </div>
1029 </div>
1030
1031 {/* Right Column - Orders and Analytics */}
1032 <div className="lg:col-span-2 space-y-6">
1033 {/* Recent Orders */}
1034 <div className="bg-surface rounded-lg shadow-md p-6">
1035 <h2 className="text-xl font-bold text-text mb-4">Recent Purchase Orders</h2>
1036
1037 {recentOrders.length === 0 ? (
1038 <p className="text-muted text-center py-8">No recent orders</p>
1039 ) : (
1040 <div className="overflow-x-auto">
1041 <table className="w-full">
1042 <thead>
1043 <tr className="border-b border-border">
1044 <th className="text-left py-3 px-4 text-sm font-semibold text-text">PO #</th>
1045 <th className="text-left py-3 px-4 text-sm font-semibold text-text">Date</th>
1046 <th className="text-left py-3 px-4 text-sm font-semibold text-text">Status</th>
1047 <th className="text-right py-3 px-4 text-sm font-semibold text-text">Items</th>
1048 <th className="text-right py-3 px-4 text-sm font-semibold text-text">Total</th>
1049 <th className="text-left py-3 px-4 text-sm font-semibold text-text">Store</th>
1050 </tr>
1051 </thead>
1052 <tbody>
1053 {recentOrders.map((order) => (
1054 <tr key={order.id} className="border-b border-border hover:bg-surface-2">
1055 <td className="py-3 px-4 text-sm">
1056 <span className="font-mono text-[#00946b]">{order.id}</span>
1057 </td>
1058 <td className="py-3 px-4 text-sm text-text">{order.date}</td>
1059 <td className="py-3 px-4">
1060 <span
1061 className={`inline-block px-2 py-1 text-xs font-semibold rounded-full ${
1062 order.status === "Received"
1063 ? "bg-green-100 text-green-800"
1064 : order.status === "Pending"
1065 ? "bg-yellow-100 text-yellow-800"
1066 : "bg-surface-2 text-text"
1067 }`}
1068 >
1069 {order.status}
1070 </span>
1071 </td>
1072 <td className="py-3 px-4 text-sm text-right text-text">{order.items}</td>
1073 <td className="py-3 px-4 text-sm text-right font-semibold text-text">
1074 ${order.total.toLocaleString()}
1075 </td>
1076 <td className="py-3 px-4 text-sm text-muted">{order.store}</td>
1077 </tr>
1078 ))}
1079 </tbody>
1080 </table>
1081 </div>
1082 )}
1083 </div>
1084
1085 {/* Charts Row */}
1086 <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
1087 {/* Order Trend Chart */}
1088 <div className="bg-surface rounded-lg shadow-md p-6">
1089 <h3 className="text-lg font-bold text-text mb-4">Order Trend</h3>
1090 <div className="h-64">
1091 <Line data={orderTrendData} options={chartOptions} />
1092 </div>
1093 </div>
1094
1095 {/* Department Spend Chart */}
1096 <div className="bg-surface rounded-lg shadow-md p-6">
1097 <h3 className="text-lg font-bold text-text mb-4">Spend by Department</h3>
1098 <div className="h-64">
1099 <Doughnut data={departmentData} options={chartOptions} />
1100 </div>
1101 </div>
1102 </div>
1103
1104 {/* Top Products */}
1105 <div className="bg-surface rounded-lg shadow-md p-6">
1106 <h3 className="text-lg font-bold text-text mb-4">Top Products Ordered</h3>
1107
1108 {topProducts.length === 0 ? (
1109 <p className="text-muted text-center py-8">No product data available</p>
1110 ) : (
1111 <div className="space-y-3">
1112 {topProducts.map((product, index) => (
1113 <div key={index} className="flex items-center justify-between p-3 bg-surface-2 rounded-lg">
1114 <div className="flex items-center gap-3">
1115 <div className="flex items-center justify-center w-8 h-8 bg-[#00946b] text-white rounded-full font-bold text-sm">
1116 {index + 1}
1117 </div>
1118 <div>
1119 <p className="font-medium text-text">{product.name}</p>
1120 {product.lastOrdered && (
1121 <p className="text-xs text-muted">Last ordered: {product.lastOrdered}</p>
1122 )}
1123 </div>
1124 </div>
1125 <div className="text-right">
1126 <p className="font-semibold text-text">${product.value.toLocaleString()}</p>
1127 <p className="text-sm text-muted">{product.quantity} units</p>
1128 </div>
1129 </div>
1130 ))}
1131 </div>
1132 )}
1133 </div>
1134 </div>
1135 </div>
1136 )}
1137
1138 {/* Contacts Tab */}
1139 {activeTab === "contacts" && (
1140 <div className="bg-surface rounded-lg shadow-md p-6">
1141 <div className="flex justify-between items-center mb-6">
1142 <h2 className="text-2xl font-bold text-text">Supplier Contacts</h2>
1143 <button
1144 onClick={() => {
1145 setEditingContact(null);
1146 setContactForm({
1147 name: "",
1148 firstName: "",
1149 lastName: "",
1150 title: "",
1151 phone: "",
1152 mobile: "",
1153 email: "",
1154 comments: "",
1155 });
1156 setShowContactForm(true);
1157 }}
1158 className="px-4 py-2 bg-[#00946b] text-white rounded-lg hover:bg-[#00543b] transition-colors"
1159 >
1160 Add Contact
1161 </button>
1162 </div>
1163
1164 {/* Contacts Grid */}
1165 {contacts.length === 0 ? (
1166 <div className="text-center py-12">
1167 <svg className="mx-auto h-12 w-12 text-muted/70" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1168 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
1169 </svg>
1170 <p className="mt-2 text-muted">No contacts yet. Add your first contact to get started.</p>
1171 </div>
1172 ) : (
1173 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
1174 {contacts.map((contact) => (
1175 <div key={contact.id} className="border border-border rounded-lg p-4 hover:shadow-md transition-shadow">
1176 <div className="flex items-start justify-between mb-3">
1177 <div className="flex items-center gap-3">
1178 <div className="w-12 h-12 bg-[#00946b] text-white rounded-full flex items-center justify-center font-bold text-lg">
1179 {contact.firstName ? contact.firstName.charAt(0) : contact.name.charAt(0)}
1180 {contact.lastName ? contact.lastName.charAt(0) : ""}
1181 </div>
1182 <div>
1183 <h3 className="font-semibold text-text">{contact.name}</h3>
1184 {contact.title && <p className="text-sm text-muted">{contact.title}</p>}
1185 </div>
1186 </div>
1187 </div>
1188
1189 <div className="space-y-2 mb-4">
1190 {contact.phone && (
1191 <div className="flex items-center gap-2 text-sm">
1192 <svg className="w-4 h-4 text-muted/70" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1193 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
1194 </svg>
1195 <span className="text-text">{contact.phone}</span>
1196 </div>
1197 )}
1198 {contact.mobile && (
1199 <div className="flex items-center gap-2 text-sm">
1200 <svg className="w-4 h-4 text-muted/70" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1201 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" />
1202 </svg>
1203 <span className="text-text">{contact.mobile}</span>
1204 </div>
1205 )}
1206 {contact.email && (
1207 <div className="flex items-center gap-2 text-sm">
1208 <svg className="w-4 h-4 text-muted/70" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1209 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
1210 </svg>
1211 <span className="text-text truncate">{contact.email}</span>
1212 </div>
1213 )}
1214 </div>
1215
1216 {contact.comments && (
1217 <p className="text-sm text-muted mb-4 italic">{contact.comments}</p>
1218 )}
1219
1220 <div className="flex gap-2">
1221 <button
1222 onClick={() => handleEditContact(contact)}
1223 className="flex-1 px-3 py-1.5 text-sm bg-surface-2 text-text rounded hover:bg-surface-2 transition-colors"
1224 >
1225 Edit
1226 </button>
1227 <button
1228 onClick={() => handleDeleteContact(contact.id)}
1229 className="flex-1 px-3 py-1.5 text-sm bg-red-50 text-red-600 rounded hover:bg-red-100 transition-colors"
1230 >
1231 Delete
1232 </button>
1233 </div>
1234 </div>
1235 ))}
1236 </div>
1237 )}
1238
1239 {/* Contact Form Modal */}
1240 {showContactForm && (
1241 <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
1242 <div className="bg-surface rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
1243 <div className="p-6">
1244 <h3 className="text-xl font-bold text-text mb-4">
1245 {editingContact ? "Edit Contact" : "Add New Contact"}
1246 </h3>
1247
1248 <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
1249 <div>
1250 <label className="block text-sm font-medium text-text mb-1">Full Name</label>
1251 <input
1252 type="text"
1253 value={contactForm.name}
1254 onChange={(e) => setContactForm({ ...contactForm, name: e.target.value })}
1255 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1256 placeholder="John Smith"
1257 />
1258 </div>
1259
1260 <div>
1261 <label className="block text-sm font-medium text-text mb-1">First Name</label>
1262 <input
1263 type="text"
1264 value={contactForm.firstName}
1265 onChange={(e) => setContactForm({ ...contactForm, firstName: e.target.value })}
1266 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1267 placeholder="John"
1268 />
1269 </div>
1270
1271 <div>
1272 <label className="block text-sm font-medium text-text mb-1">Last Name</label>
1273 <input
1274 type="text"
1275 value={contactForm.lastName}
1276 onChange={(e) => setContactForm({ ...contactForm, lastName: e.target.value })}
1277 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1278 placeholder="Smith"
1279 />
1280 </div>
1281
1282 <div>
1283 <label className="block text-sm font-medium text-text mb-1">Title</label>
1284 <input
1285 type="text"
1286 value={contactForm.title}
1287 onChange={(e) => setContactForm({ ...contactForm, title: e.target.value })}
1288 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1289 placeholder="Sales Manager"
1290 />
1291 </div>
1292
1293 <div>
1294 <label className="block text-sm font-medium text-text mb-1">Phone</label>
1295 <input
1296 type="tel"
1297 value={contactForm.phone}
1298 onChange={(e) => setContactForm({ ...contactForm, phone: e.target.value })}
1299 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1300 placeholder="(555) 123-4567"
1301 />
1302 </div>
1303
1304 <div>
1305 <label className="block text-sm font-medium text-text mb-1">Mobile</label>
1306 <input
1307 type="tel"
1308 value={contactForm.mobile}
1309 onChange={(e) => setContactForm({ ...contactForm, mobile: e.target.value })}
1310 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1311 placeholder="(555) 987-6543"
1312 />
1313 </div>
1314
1315 <div className="md:col-span-2">
1316 <label className="block text-sm font-medium text-text mb-1">Email</label>
1317 <input
1318 type="email"
1319 value={contactForm.email}
1320 onChange={(e) => setContactForm({ ...contactForm, email: e.target.value })}
1321 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1322 placeholder="john.smith@supplier.com"
1323 />
1324 </div>
1325
1326 <div className="md:col-span-2">
1327 <label className="block text-sm font-medium text-text mb-1">Comments</label>
1328 <textarea
1329 value={contactForm.comments}
1330 onChange={(e) => setContactForm({ ...contactForm, comments: e.target.value })}
1331 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent resize-none"
1332 rows={3}
1333 placeholder="Additional notes about this contact..."
1334 />
1335 </div>
1336 </div>
1337
1338 <div className="flex gap-3 mt-6">
1339 <button
1340 onClick={handleSaveContact}
1341 className="flex-1 px-4 py-2 bg-[#00946b] text-white rounded-lg hover:bg-[#00543b] transition-colors"
1342 >
1343 {editingContact ? "Update Contact" : "Add Contact"}
1344 </button>
1345 <button
1346 onClick={() => {
1347 setShowContactForm(false);
1348 setEditingContact(null);
1349 }}
1350 className="flex-1 px-4 py-2 bg-surface-2 text-text rounded-lg hover:bg-surface-2 transition-colors"
1351 >
1352 Cancel
1353 </button>
1354 </div>
1355 </div>
1356 </div>
1357 </div>
1358 )}
1359 </div>
1360 )}
1361
1362 {/* Store Codes Tab */}
1363 {activeTab === "stores" && (
1364 <div className="bg-surface rounded-lg shadow-md p-6">
1365 <h2 className="text-2xl font-bold text-text mb-6">Store-Specific Supplier Codes</h2>
1366 <p className="text-muted mb-6">
1367 Map this supplier to specific codes used at each of your stores. Changes are automatically saved.
1368 </p>
1369
1370 {storeCodes.length === 0 ? (
1371 <div className="text-center py-12">
1372 <p className="text-muted">No stores configured</p>
1373 </div>
1374 ) : (
1375 <div className="overflow-x-auto">
1376 <table className="w-full">
1377 <thead>
1378 <tr className="border-b-2 border-border">
1379 <th className="text-left py-3 px-4 font-semibold text-text">Store ID</th>
1380 <th className="text-left py-3 px-4 font-semibold text-text">Store Name</th>
1381 <th className="text-left py-3 px-4 font-semibold text-text">Supplier Code</th>
1382 </tr>
1383 </thead>
1384 <tbody>
1385 {storeCodes.map((store) => (
1386 <tr key={store.storeId} className="border-b border-border hover:bg-surface-2">
1387 <td className="py-3 px-4 text-sm text-muted">{store.storeId}</td>
1388 <td className="py-3 px-4 text-sm font-medium text-text">{store.storeName}</td>
1389 <td className="py-3 px-4">
1390 <input
1391 type="text"
1392 value={store.code}
1393 onChange={(e) => handleUpdateStoreCode(store.storeId, e.target.value)}
1394 className="w-full max-w-xs px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1395 placeholder="Enter supplier code..."
1396 />
1397 </td>
1398 </tr>
1399 ))}
1400 </tbody>
1401 </table>
1402 </div>
1403 )}
1404 </div>
1405 )}
1406
1407 {/* Ordering Tab */}
1408 {activeTab === "ordering" && (
1409 <div className="space-y-6">
1410 {/* Freight Configuration */}
1411 <div className="bg-surface rounded-lg shadow-md p-6">
1412 <h2 className="text-2xl font-bold text-text mb-6">Freight Configuration</h2>
1413
1414 <div className="space-y-6">
1415 <div className="flex items-center gap-3">
1416 <input
1417 type="checkbox"
1418 id="allFree"
1419 checked={freightConfig.allFree}
1420 onChange={(e) => handleUpdateFreightConfig("allFree", e.target.checked)}
1421 className="w-5 h-5 text-[#00946b] border-border rounded focus:ring-[#00946b]"
1422 />
1423 <label htmlFor="allFree" className="text-sm font-medium text-text">
1424 All Freight Free (disables all other freight charges)
1425 </label>
1426 </div>
1427
1428 {!freightConfig.allFree && (
1429 <>
1430 <div>
1431 <label className="block text-sm font-medium text-text mb-2">Fixed Freight Cost</label>
1432 <div className="flex items-center gap-2">
1433 <span className="text-muted">$</span>
1434 <input
1435 type="text"
1436 value={freightConfig.fixedCost}
1437 onChange={(e) => handleUpdateFreightConfig("fixedCost", e.target.value)}
1438 className="w-32 px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1439 placeholder="0.00"
1440 />
1441 <span className="text-sm text-muted">Flat rate per order</span>
1442 </div>
1443 </div>
1444
1445 <div className="border-t border-border pt-6">
1446 <h3 className="text-lg font-semibold text-text mb-4">Value-Based Freight Tiers</h3>
1447
1448 {/* Tier 1 */}
1449 <div className="bg-surface-2 p-4 rounded-lg mb-4">
1450 <h4 className="text-sm font-semibold text-text mb-3">Tier 1: Order Value up to</h4>
1451 <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
1452 <div>
1453 <label className="block text-sm text-muted mb-1">Maximum Value</label>
1454 <div className="flex items-center gap-2">
1455 <span className="text-muted">$</span>
1456 <input
1457 type="text"
1458 value={freightConfig.value1High}
1459 onChange={(e) => handleUpdateFreightConfig("value1High", e.target.value)}
1460 className="flex-1 px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1461 placeholder="500.00"
1462 />
1463 </div>
1464 </div>
1465 <div>
1466 <label className="block text-sm text-muted mb-1">Freight Charge</label>
1467 <div className="flex items-center gap-2">
1468 <span className="text-muted">$</span>
1469 <input
1470 type="text"
1471 value={freightConfig.value1Charge}
1472 onChange={(e) => handleUpdateFreightConfig("value1Charge", e.target.value)}
1473 className="flex-1 px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1474 placeholder="25.00"
1475 />
1476 </div>
1477 </div>
1478 </div>
1479 </div>
1480
1481 {/* Tier 2 */}
1482 <div className="bg-surface-2 p-4 rounded-lg mb-4">
1483 <h4 className="text-sm font-semibold text-text mb-3">Tier 2: Order Value between</h4>
1484 <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
1485 <div>
1486 <label className="block text-sm text-muted mb-1">Minimum Value</label>
1487 <div className="flex items-center gap-2">
1488 <span className="text-muted">$</span>
1489 <input
1490 type="text"
1491 value={freightConfig.value2Low}
1492 onChange={(e) => handleUpdateFreightConfig("value2Low", e.target.value)}
1493 className="flex-1 px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1494 placeholder="500.00"
1495 />
1496 </div>
1497 </div>
1498 <div>
1499 <label className="block text-sm text-muted mb-1">Maximum Value</label>
1500 <div className="flex items-center gap-2">
1501 <span className="text-muted">$</span>
1502 <input
1503 type="text"
1504 value={freightConfig.value2High}
1505 onChange={(e) => handleUpdateFreightConfig("value2High", e.target.value)}
1506 className="flex-1 px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1507 placeholder="1000.00"
1508 />
1509 </div>
1510 </div>
1511 <div>
1512 <label className="block text-sm text-muted mb-1">Freight Charge</label>
1513 <div className="flex items-center gap-2">
1514 <span className="text-muted">$</span>
1515 <input
1516 type="text"
1517 value={freightConfig.value2Charge}
1518 onChange={(e) => handleUpdateFreightConfig("value2Charge", e.target.value)}
1519 className="flex-1 px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1520 placeholder="15.00"
1521 />
1522 </div>
1523 </div>
1524 </div>
1525 </div>
1526
1527 {/* Freight Free Threshold */}
1528 <div className="bg-green-50 p-4 rounded-lg">
1529 <h4 className="text-sm font-semibold text-text mb-3">Freight-Free Threshold</h4>
1530 <div>
1531 <label className="block text-sm text-muted mb-1">Free shipping on orders above</label>
1532 <div className="flex items-center gap-2">
1533 <span className="text-muted">$</span>
1534 <input
1535 type="text"
1536 value={freightConfig.aboveFree}
1537 onChange={(e) => handleUpdateFreightConfig("aboveFree", e.target.value)}
1538 className="w-40 px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1539 placeholder="1000.00"
1540 />
1541 </div>
1542 </div>
1543 </div>
1544 </div>
1545
1546 <div>
1547 <label className="block text-sm font-medium text-text mb-2">Freight Comments</label>
1548 <textarea
1549 value={freightConfig.comments}
1550 onChange={(e) => handleUpdateFreightConfig("comments", e.target.value)}
1551 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent resize-none"
1552 rows={3}
1553 placeholder="Additional freight notes or special conditions..."
1554 />
1555 </div>
1556 </>
1557 )}
1558 </div>
1559 </div>
1560
1561 {/* Order Settings */}
1562 <div className="bg-surface rounded-lg shadow-md p-6">
1563 <h2 className="text-2xl font-bold text-text mb-6">Purchase Order Settings</h2>
1564
1565 <div className="space-y-6">
1566 {/* Email Configuration */}
1567 <div className="border-b border-border pb-6">
1568 <h3 className="text-lg font-semibold text-text mb-4">Email Configuration</h3>
1569 <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
1570 <div>
1571 <label className="block text-sm font-medium text-text mb-1">Email Address</label>
1572 <input
1573 type="email"
1574 value={orderSettings.email}
1575 onChange={(e) => handleUpdateOrderSettings("email", e.target.value)}
1576 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1577 placeholder="orders@supplier.com"
1578 />
1579 </div>
1580 <div>
1581 <label className="block text-sm font-medium text-text mb-1">Email Subject</label>
1582 <input
1583 type="text"
1584 value={orderSettings.emailSubject}
1585 onChange={(e) => handleUpdateOrderSettings("emailSubject", e.target.value)}
1586 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1587 placeholder="Purchase Order from..."
1588 />
1589 </div>
1590 </div>
1591
1592 <div className="mt-4 space-y-2">
1593 <div className="flex items-center gap-3">
1594 <input
1595 type="checkbox"
1596 id="emailNormal"
1597 checked={orderSettings.emailNormal}
1598 onChange={(e) => handleUpdateOrderSettings("emailNormal", e.target.checked)}
1599 className="w-5 h-5 text-[#00946b] border-border rounded focus:ring-[#00946b]"
1600 />
1601 <label htmlFor="emailNormal" className="text-sm font-medium text-text">
1602 Send email in normal format (text/HTML)
1603 </label>
1604 </div>
1605 <div className="flex items-center gap-3">
1606 <input
1607 type="checkbox"
1608 id="emailPDF"
1609 checked={orderSettings.emailPDF}
1610 onChange={(e) => handleUpdateOrderSettings("emailPDF", e.target.checked)}
1611 className="w-5 h-5 text-[#00946b] border-border rounded focus:ring-[#00946b]"
1612 />
1613 <label htmlFor="emailPDF" className="text-sm font-medium text-text">
1614 Attach PDF version of purchase order
1615 </label>
1616 </div>
1617 </div>
1618 </div>
1619
1620 {/* Order Format */}
1621 <div className="border-b border-border pb-6">
1622 <h3 className="text-lg font-semibold text-text mb-4">Order Format Template</h3>
1623 <select
1624 value={orderSettings.format}
1625 onChange={(e) => handleUpdateOrderSettings("format", e.target.value)}
1626 className="w-full max-w-md px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1627 >
1628 <option value="401">Format 401 - Standard Purchase Order</option>
1629 <option value="402">Format 402 - Detailed Line Items</option>
1630 <option value="403">Format 403 - Summary Only</option>
1631 <option value="404">Format 404 - Custom Template</option>
1632 </select>
1633 </div>
1634
1635 {/* Order Controls */}
1636 <div>
1637 <h3 className="text-lg font-semibold text-text mb-4">Order Validation Controls</h3>
1638 <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
1639 {/* Minimums */}
1640 <div className="space-y-4">
1641 <h4 className="text-sm font-semibold text-text">Minimum Requirements</h4>
1642 <div>
1643 <label className="block text-sm text-muted mb-1">Min Order Value</label>
1644 <div className="flex items-center gap-2">
1645 <span className="text-muted">$</span>
1646 <input
1647 type="text"
1648 value={orderSettings.minOrderValue}
1649 onChange={(e) => handleUpdateOrderSettings("minOrderValue", e.target.value)}
1650 className="flex-1 px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1651 placeholder="100.00"
1652 />
1653 </div>
1654 </div>
1655 <div>
1656 <label className="block text-sm text-muted mb-1">Min Retail Value</label>
1657 <div className="flex items-center gap-2">
1658 <span className="text-muted">$</span>
1659 <input
1660 type="text"
1661 value={orderSettings.minRetailValue}
1662 onChange={(e) => handleUpdateOrderSettings("minRetailValue", e.target.value)}
1663 className="flex-1 px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1664 placeholder="0.00"
1665 />
1666 </div>
1667 </div>
1668 <div>
1669 <label className="block text-sm text-muted mb-1">Min Units</label>
1670 <input
1671 type="text"
1672 value={orderSettings.minUnits}
1673 onChange={(e) => handleUpdateOrderSettings("minUnits", e.target.value)}
1674 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1675 placeholder="10"
1676 />
1677 </div>
1678 <div>
1679 <label className="block text-sm text-muted mb-1">Min Lines</label>
1680 <input
1681 type="text"
1682 value={orderSettings.minLines}
1683 onChange={(e) => handleUpdateOrderSettings("minLines", e.target.value)}
1684 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1685 placeholder="1"
1686 />
1687 </div>
1688 </div>
1689
1690 {/* Maximums */}
1691 <div className="space-y-4">
1692 <h4 className="text-sm font-semibold text-text">Maximum Limits</h4>
1693 <div>
1694 <label className="block text-sm text-muted mb-1">Max Order Value</label>
1695 <div className="flex items-center gap-2">
1696 <span className="text-muted">$</span>
1697 <input
1698 type="text"
1699 value={orderSettings.maxOrderValue}
1700 onChange={(e) => handleUpdateOrderSettings("maxOrderValue", e.target.value)}
1701 className="flex-1 px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1702 placeholder="Leave blank for no limit"
1703 />
1704 </div>
1705 </div>
1706 <div>
1707 <label className="block text-sm text-muted mb-1">Max Retail Value</label>
1708 <div className="flex items-center gap-2">
1709 <span className="text-muted">$</span>
1710 <input
1711 type="text"
1712 value={orderSettings.maxRetailValue}
1713 onChange={(e) => handleUpdateOrderSettings("maxRetailValue", e.target.value)}
1714 className="flex-1 px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1715 placeholder="Leave blank for no limit"
1716 />
1717 </div>
1718 </div>
1719 <div>
1720 <label className="block text-sm text-muted mb-1">Max Units</label>
1721 <input
1722 type="text"
1723 value={orderSettings.maxUnits}
1724 onChange={(e) => handleUpdateOrderSettings("maxUnits", e.target.value)}
1725 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1726 placeholder="Leave blank for no limit"
1727 />
1728 </div>
1729 <div>
1730 <label className="block text-sm text-muted mb-1">Max Lines</label>
1731 <input
1732 type="text"
1733 value={orderSettings.maxLines}
1734 onChange={(e) => handleUpdateOrderSettings("maxLines", e.target.value)}
1735 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1736 placeholder="Leave blank for no limit"
1737 />
1738 </div>
1739 </div>
1740 </div>
1741 </div>
1742
1743 {/* Order Message */}
1744 <div>
1745 <h3 className="text-lg font-semibold text-text mb-4">Time-Based Order Message</h3>
1746 <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
1747 <div>
1748 <label className="block text-sm text-muted mb-1">Start Date</label>
1749 <input
1750 type="date"
1751 value={orderSettings.messageStartDate}
1752 onChange={(e) => handleUpdateOrderSettings("messageStartDate", e.target.value)}
1753 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1754 />
1755 </div>
1756 <div>
1757 <label className="block text-sm text-muted mb-1">End Date</label>
1758 <input
1759 type="date"
1760 value={orderSettings.messageEndDate}
1761 onChange={(e) => handleUpdateOrderSettings("messageEndDate", e.target.value)}
1762 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
1763 />
1764 </div>
1765 </div>
1766 <textarea
1767 value={orderSettings.orderMessage}
1768 onChange={(e) => handleUpdateOrderSettings("orderMessage", e.target.value)}
1769 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent resize-none"
1770 rows={4}
1771 placeholder="Enter a message that will appear on orders during the specified date range..."
1772 />
1773 </div>
1774 </div>
1775 </div>
1776 </div>
1777 )}
1778 </div>
1779 </div>
1780 );
1781}