3import { useEffect, useState } from "react";
4import { apiClient } from "@/lib/client/apiClient";
5import { Icon } from '@/contexts/IconContext';
10 f102?: number; // Latitude
11 f103?: number; // Longitude
12 f150?: string; // Phone
13 f151?: string; // Email
14 f164?: string; // Store Type
15 f185?: number; // Config Type
16 f186?: number; // Parent Location
19interface StoreFormData {
27export default function StoresPage() {
28 const [stores, setStores] = useState<Store[]>([]);
29 const [filteredStores, setFilteredStores] = useState<Store[]>([]);
30 const [loading, setLoading] = useState(true);
31 const [searchTerm, setSearchTerm] = useState("");
32 const [showModal, setShowModal] = useState(false);
33 const [formData, setFormData] = useState<StoreFormData>({
46 }, [stores, searchTerm]);
48 const loadStores = async () => {
51 const result = await apiClient.getLocations({ source: 'elink' });
53 if (result.success && (result.data as any)?.DATS) {
54 setStores((result.data as any).DATS);
57 console.error("Error loading stores:", error);
63 const applyFilters = () => {
65 setFilteredStores(stores);
69 const search = searchTerm.toLowerCase();
70 const filtered = stores.filter((store) => {
72 store.f100?.toString().includes(search) ||
73 store.f101?.toLowerCase().includes(search) ||
74 store.f150?.toLowerCase().includes(search) ||
75 store.f151?.toLowerCase().includes(search)
79 setFilteredStores(filtered);
82 const openNewStoreModal = () => {
92 const openEditModal = (store: Store) => {
94 id: store.f100.toString(),
95 name: store.f101 || "",
96 phone: store.f150 || "",
97 email: store.f151 || "",
103 const handleSave = async () => {
105 // Validate required fields
106 if (!formData.name) {
107 alert("Store name is required");
111 const locationData = {
112 id: formData.id ? parseInt(formData.id) : undefined,
114 phone: formData.phone || undefined,
115 email: formData.email || undefined,
118 const result = await apiClient.saveLocation(locationData);
120 if (result.success) {
123 alert(formData.isNew ? "Store created successfully" : "Store updated successfully");
125 alert(result.error || "Failed to save store");
128 console.error("Error saving store:", error);
129 alert("Failed to save store");
133 const exportToExcel = () => {
134 // Create CSV content (Excel will open CSV files)
135 const headers = ['ID', 'Name', 'Phone', 'Email', 'Latitude', 'Longitude', 'Store Type'];
138 ...stores.map(store => [
140 `"${(store.f101 || '').replace(/"/g, '""')}"`,
141 `"${(store.f150 || '').replace(/"/g, '""')}"`,
142 `"${(store.f151 || '').replace(/"/g, '""')}"`,
145 `"${(store.f164 || '').replace(/"/g, '""')}"`,
149 const csvContent = csvRows.join('\n');
150 const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
151 const link = document.createElement('a');
152 const url = URL.createObjectURL(blob);
154 link.setAttribute('href', url);
155 link.setAttribute('download', `Stores_${new Date().toISOString().split('T')[0]}.csv`);
156 link.style.visibility = 'hidden';
157 document.body.appendChild(link);
159 document.body.removeChild(link);
164 <div className="p-6 min-h-screen">
165 <div className="animate-pulse space-y-4">
166 <div className="h-8 bg-surface-2 rounded w-1/4"></div>
167 <div className="h-64 bg-surface-2 rounded"></div>
174 <div className="p-6 min-h-screen">
176 <div className="mb-6">
177 <h1 className="text-3xl font-bold text-text mb-2 flex items-center gap-3">
178 <Icon name="store" size={32} />
181 <p className="text-muted">
182 Manage store locations, settings, and configurations
187 <div className="grid grid-cols-2 md:grid-cols-3 gap-4 mb-6">
189 onClick={openNewStoreModal}
190 className="bg-surface rounded-lg shadow p-6 hover:shadow-lg transition-shadow flex flex-col items-center justify-center gap-3 border-2 border-transparent hover:border-brand"
192 <Icon name="add_business" size={48} className="text-brand" />
193 <span className="font-semibold text-text">New Store</span>
197 href="/pages/settings/stores/topology"
198 className="bg-surface rounded-lg shadow p-6 hover:shadow-lg transition-shadow flex flex-col items-center justify-center gap-3 border-2 border-transparent hover:border-brand"
200 <Icon name="hub" size={48} className="text-brand" />
201 <span className="font-semibold text-text">Topology</span>
205 onClick={exportToExcel}
206 className="bg-surface rounded-lg shadow p-6 hover:shadow-lg transition-shadow flex flex-col items-center justify-center gap-3 border-2 border-transparent hover:border-brand"
208 <Icon name="file_download" size={48} className="text-brand" />
209 <span className="font-semibold text-text">Excel Export</span>
214 <div className="bg-surface rounded-lg shadow p-4 mb-6">
215 <div className="flex items-center gap-4">
216 <div className="flex-1">
219 placeholder="Search stores..."
221 onChange={(e) => setSearchTerm(e.target.value)}
222 className="w-full px-4 py-2 border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent"
225 <div className="text-sm text-muted">
226 Displaying {filteredStores.length} of {stores.length} stores
232 <div className="bg-surface rounded-lg shadow overflow-hidden">
233 <div className="overflow-x-auto">
234 <table className="w-full">
235 <thead className="bg-[var(--brand)] text-surface">
237 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
240 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
243 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
246 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
249 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
254 <tbody className="bg-surface divide-y divide-border">
255 {filteredStores.map((store) => (
256 <tr key={store.f100} className="hover:bg-surface-2">
257 <td className="px-6 py-4 whitespace-nowrap text-sm text-text">
260 <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-text">
263 <td className="px-6 py-4 whitespace-nowrap text-sm text-muted">
266 <td className="px-6 py-4 whitespace-nowrap text-sm text-muted">
269 href={`mailto:${store.f151}`}
270 className="text-brand hover:underline flex items-center gap-1"
272 <Icon name="email" size={16} />
279 <td className="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
281 onClick={() => openEditModal(store)}
282 className="text-brand hover:text-brand2"
287 href={`/pages/settings/stores/${store.f100}`}
288 className="text-brand hover:text-brand2"
300 {/* Edit/Create Modal */}
302 <div className="fixed inset-0 bg-black/30 backdrop-blur-sm flex items-center justify-center z-50 p-4 overflow-y-auto">
303 <div className="bg-surface/95 backdrop-blur-md rounded-lg shadow-xl max-w-2xl w-full my-8">
304 <div className="bg-brand/90 backdrop-blur-sm text-white px-6 py-4 rounded-t-lg flex items-center justify-between">
305 <h2 className="text-2xl font-bold text-white">
306 {formData.isNew ? "Create New Store" : "Edit Store"}
309 onClick={() => setShowModal(false)}
310 className="text-white hover:text-white/80"
312 <Icon name="close" size={24} />
316 <div className="p-6 bg-surface/95 backdrop-blur-md rounded-b-lg">
317 <div className="space-y-4">
320 <label className="block text-sm font-medium text-text mb-1">
325 value={formData.id || ""}
326 onChange={(e) => setFormData({ ...formData, id: e.target.value })}
327 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
328 placeholder="Leave blank for auto-assign"
330 <p className="text-xs text-muted mt-1">Rarely used, generally left blank</p>
335 <label className="block text-sm font-medium text-text mb-1">
340 value={formData.name}
341 onChange={(e) => setFormData({ ...formData, name: e.target.value })}
342 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
345 <p className="text-xs text-muted mt-1">Used as name of the store on your reports</p>
349 <label className="block text-sm font-medium text-text mb-1">
354 value={formData.phone}
355 onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
356 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
361 <label className="block text-sm font-medium text-text mb-1">
366 value={formData.email}
367 onChange={(e) => setFormData({ ...formData, email: e.target.value })}
368 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
373 <div className="mt-6 flex justify-end gap-3">
375 onClick={() => setShowModal(false)}
376 className="px-6 py-2 border border-border rounded-lg hover:bg-surface-2 transition-colors"
382 className="bg-[#00543b] text-white px-6 py-2 rounded-lg hover:bg-[#003d2b] transition-colors"