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";
2
3import { useState, useEffect } from "react";
4import { Icon } from '@/contexts/IconContext';
5
6interface ApiRoute {
7 path: string;
8 methods: string[];
9 description: string;
10 category?: string;
11 accessLevel?: "pos" | "merchant";
12 testStatus?: "success" | "failed" | "untested";
13 testUrl?: string;
14}
15
16interface TestResult {
17 name: string;
18 url: string;
19 status: number;
20 success: boolean;
21}
22
23export default function ApiDocumentationPage() {
24 const [searchTerm, setSearchTerm] = useState("");
25 const [selectedType, setSelectedType] = useState<"all" | "elink" | "openapi" | "fd1">("all");
26 const [selectedAccess, setSelectedAccess] = useState<"all" | "pos" | "merchant">("all");
27 const [testResults, setTestResults] = useState<TestResult[]>([]);
28 const [testDataLoaded, setTestDataLoaded] = useState(false);
29
30 // Load test results from the JSON files
31 useEffect(() => {
32 const loadTestResults = async () => {
33 try {
34 const response = await fetch('/images/api-test-results-murwillumbah-2026-01-20T05-57-05.json');
35 const data = await response.json();
36 setTestResults(data.results || []);
37 setTestDataLoaded(true);
38 } catch (error) {
39 console.error('Failed to load test results:', error);
40 setTestDataLoaded(true);
41 }
42 };
43 loadTestResults();
44 }, []);
45
46 // Helper function to get test status for a route
47 const getTestStatus = (routePath: string): "success" | "failed" | "untested" => {
48 if (!testDataLoaded) return "untested";
49
50 // Normalize the route path for matching
51 const normalizedPath = routePath.replace(/\[id\]/g, '1').replace(/\[.*?\]/g, '1');
52
53 // Find matching test result
54 const testResult = testResults.find(result => {
55 const testPath = result.url.split('?')[0]; // Remove query params
56 return testPath === normalizedPath || result.url === normalizedPath;
57 });
58
59 if (!testResult) return "untested";
60 return testResult.success ? "success" : "failed";
61 };
62
63 // eLink/BUCK API Routes
64 const elinkRoutes: ApiRoute[] = [
65 // eLink Direct Routes - POS Accessible
66 { path: "/api/v1/elink/products", methods: ["GET"], description: "Get products using eLink protocol (f-codes)", accessLevel: "pos" },
67 { path: "/api/v1/elink/customers", methods: ["GET"], description: "Get customers using eLink protocol", accessLevel: "pos" },
68 { path: "/api/v1/elink/customers/[id]", methods: ["GET", "PUT"], description: "Individual customer via eLink", accessLevel: "merchant" },
69 { path: "/api/v1/elink/departments", methods: ["GET"], description: "Get departments using eLink protocol", accessLevel: "pos" },
70 { path: "/api/v1/elink/suppliers", methods: ["GET"], description: "Get suppliers using eLink protocol", accessLevel: "pos" },
71
72 // eLink Sales - Mixed Access
73 { path: "/api/v1/elink/sales", methods: ["GET"], description: "List sales using ELINK protocol (retailmax.elink.sale.list)", accessLevel: "merchant" },
74 { path: "/api/v1/elink/sales/[id]", methods: ["GET", "DELETE"], description: "Fetch sale details with line items or void sale", accessLevel: "merchant" },
75 { path: "/api/v1/elink/sales/flat", methods: ["GET"], description: "Flattened sales list with embedded line items", accessLevel: "merchant" },
76 { path: "/api/v1/elink/sales-report", methods: ["GET"], description: "Sales report data for POS (retailmax.elink.sale.totals)", accessLevel: "pos" },
77
78 // eLink Staff & Organization - Merchant Only
79 { path: "/api/v1/elink/locations", methods: ["GET"], description: "Get store locations using eLink", accessLevel: "merchant" },
80 { path: "/api/v1/elink/staff", methods: ["GET"], description: "Staff list (retailmax.elink.staff.list)", accessLevel: "merchant" },
81 { path: "/api/v1/elink/staff/used", methods: ["GET"], description: "Used staff IDs (retailmax.elink.staff.used.list)", accessLevel: "merchant" },
82 { path: "/api/v1/elink/staff/rights", methods: ["GET"], description: "Staff access rights", accessLevel: "merchant" },
83 { path: "/api/v1/elink/employees", methods: ["GET", "POST"], description: "Employees list/edit (retailmax.elink.employee.list)", accessLevel: "merchant" },
84 { path: "/api/v1/elink/topology", methods: ["GET"], description: "System topology information", accessLevel: "merchant" },
85
86 // BUCK Protocol Routes - Mixed Access
87 { path: "/api/v1/buck/stocktakes", methods: ["GET"], description: "List stocktakes via BUCK (retailmax.elink.stocktake.list)", accessLevel: "pos" },
88 { path: "/api/v1/buck/stocktake/status", methods: ["GET"], description: "Get stocktake status by ID (retailmax.elink.stocktake.status)", accessLevel: "merchant" },
89 { path: "/api/v1/buck/loyalty/campaigns", methods: ["GET", "POST"], description: "List/create loyalty campaigns (retailmax.elink.loyalty.list/edit)", accessLevel: "pos" },
90 { path: "/api/v1/buck/serial-items", methods: ["GET"], description: "List serial items (retailmax.elink.serialitem.list)", accessLevel: "pos" },
91 { path: "/api/v1/buck/staff-usage", methods: ["GET"], description: "Staff login activity (retailmax.elink.staff.usage)", accessLevel: "merchant" },
92 { path: "/api/v1/buck/staff-usage-detail", methods: ["GET"], description: "Detailed staff activity by ID", accessLevel: "merchant" },
93
94 // Convenience/Legacy Routes - Mixed Access
95 { path: "/api/v1/locations", methods: ["GET", "POST"], description: "Store locations with CRUD (retailmax.elink.locations + DATI)", accessLevel: "pos" },
96 { path: "/api/v1/departments", methods: ["GET"], description: "Department list (retailmax.elink.departments)", accessLevel: "merchant" },
97 { path: "/api/v1/departments/hierarchy", methods: ["GET"], description: "Department hierarchy structure", accessLevel: "pos" },
98 { path: "/api/v1/staff", methods: ["GET"], description: "Staff list (retailmax.elink.staff)", accessLevel: "merchant" },
99 { path: "/api/v1/payment-types", methods: ["GET"], description: "Payment methods (retailmax.elink.paymenttypes)", accessLevel: "merchant" },
100 { path: "/api/v1/discounts", methods: ["GET"], description: "Discounts list (retailmax.elink.discounts)", accessLevel: "merchant" },
101 { path: "/api/v1/stocktake", methods: ["GET"], description: "Stocktakes (retailmax.elink.stocktake.list)", accessLevel: "merchant" },
102 { path: "/api/v1/stats", methods: ["GET"], description: "General statistics endpoint", accessLevel: "merchant" },
103 { path: "/api/v1/stats/today", methods: ["GET"], description: "Today's stats snapshot", accessLevel: "merchant" },
104 { path: "/api/v1/stats/hourly-sales", methods: ["GET"], description: "⚠️ 404 NOT FOUND - Hourly sales breakdown (not implemented)", accessLevel: "merchant" },
105 { path: "/api/v1/stats/daily-sales", methods: ["GET"], description: "⚠️ 404 NOT FOUND - Daily sales trends (not implemented)", accessLevel: "merchant" },
106 { path: "/api/v1/stats/top-products", methods: ["GET"], description: "⚠️ 404 NOT FOUND - Top selling products (not implemented)", accessLevel: "merchant" },
107 { path: "/api/v1/stats/store-performance", methods: ["GET"], description: "⚠️ 404 NOT FOUND - Store performance metrics (not implemented)", accessLevel: "merchant" },
108 { path: "/api/v1/stats/today-payment", methods: ["GET"], description: "Today's payment breakdown", accessLevel: "merchant" },
109 { path: "/api/v1/stats/teller-performance", methods: ["GET"], description: "Teller performance metrics", accessLevel: "merchant" },
110
111 // Sales Routes - Merchant Only
112 { path: "/api/v1/sales", methods: ["GET"], description: "General sales endpoint", accessLevel: "merchant" },
113 { path: "/api/v1/sales/stats", methods: ["GET"], description: "Sales statistics by type (retailmax.elink.stats.*)", accessLevel: "merchant" },
114 { path: "/api/v1/sales/totals", methods: ["GET"], description: "Today's sales totals (retailmax.elink.stats.today)", accessLevel: "merchant" },
115 { path: "/api/v1/sales/list", methods: ["GET"], description: "Sales transactions list (retailmax.elink.salelist)", accessLevel: "merchant" },
116 { path: "/api/v1/sales/search", methods: ["GET"], description: "Search sales (retailmax.elink.salesearch)", accessLevel: "merchant" },
117 { path: "/api/v1/sales/recent", methods: ["GET"], description: "Recent sales transactions", accessLevel: "merchant" },
118 { path: "/api/v1/sales/picking", methods: ["GET"], description: "Sales picking list", accessLevel: "merchant" },
119 { path: "/api/v1/invoices", methods: ["GET"], description: "Invoice list", accessLevel: "merchant" },
120
121 // Reports Routes - Mixed Access
122 { path: "/api/v1/reports/sales-totals", methods: ["GET"], description: "Sales totals report", accessLevel: "pos" },
123 { path: "/api/v1/reports/sales-detail", methods: ["GET"], description: "Detailed sales breakdown", accessLevel: "pos" },
124 { path: "/api/v1/reports/sales-cube", methods: ["GET"], description: "Sales cube analysis (retailmax.elink.report.salescube)", accessLevel: "pos" },
125 { path: "/api/v1/reports/trading-summary", methods: ["GET"], description: "Trading summary by date/location (retailmax.elink.summary.trading)", accessLevel: "merchant" },
126 { path: "/api/v1/reports/product-performance", methods: ["GET"], description: "Product performance metrics", accessLevel: "merchant" },
127 { path: "/api/v1/reports/sales-grouped", methods: ["GET"], description: "Grouped sales analytics", accessLevel: "merchant" },
128 { path: "/api/v1/reports/purchase-orders", methods: ["GET"], description: "Purchase order reports", accessLevel: "merchant" },
129 { path: "/api/v1/reports/advisor/duplicate-descriptions", methods: ["GET"], description: "Duplicate product descriptions", accessLevel: "merchant" },
130
131 // Accounts Routes - Merchant Only
132 { path: "/api/v1/accounts", methods: ["GET"], description: "Customer accounts (retailmax.elink.accounts)", accessLevel: "merchant" },
133 { path: "/api/v1/accounts/customers", methods: ["GET"], description: "Customer accounts list", accessLevel: "merchant" },
134 { path: "/api/v1/accounts/sales", methods: ["GET"], description: "Account sales history", accessLevel: "merchant" },
135 { path: "/api/v1/accounts/financial", methods: ["GET"], description: "Account financial transactions", accessLevel: "merchant" },
136
137 // Products & Inventory Routes - Mixed Access
138 { path: "/api/v1/products/stock-alerts", methods: ["GET"], description: "Low stock alerts", accessLevel: "pos" },
139 { path: "/api/v1/products/stock-adjustment", methods: ["POST"], description: "Adjust product stock levels (DATI goodsin.edit)", accessLevel: "merchant" },
140 { path: "/api/v1/inventory/movements", methods: ["GET"], description: "Stock movement history", accessLevel: "pos" },
141 { path: "/api/v1/inventory/stock-levels", methods: ["GET"], description: "Current stock levels", accessLevel: "pos" },
142
143 // Analytics Routes - Merchant Only
144 { path: "/api/v1/analytics/pareto", methods: ["GET"], description: "Pareto analysis (80/20 rule)", accessLevel: "merchant" },
145 { path: "/api/v1/analytics/product-performance", methods: ["GET"], description: "Product performance analytics", accessLevel: "merchant" },
146
147 // Other Routes - Merchant Only
148 { path: "/api/v1/price-changes", methods: ["GET", "POST"], description: "Price change overlays (retailmax.elink.pricechange.list)", accessLevel: "merchant" },
149 { path: "/api/v1/printing/pending", methods: ["GET"], description: "Pending print jobs", accessLevel: "merchant" },
150 { path: "/api/v1/loyalty/campaigns", methods: ["GET"], description: "Legacy loyalty campaigns endpoint", accessLevel: "merchant" },
151 { path: "/api/v1/contact-logs", methods: ["GET", "POST"], description: "Customer contact logs", accessLevel: "merchant" },
152 { path: "/api/v1/config/base-settings", methods: ["GET"], description: "System base configuration settings", accessLevel: "merchant" },
153 { path: "/api/v1/messaging/config", methods: ["GET"], description: "Messaging configuration", accessLevel: "merchant" },
154 { path: "/api/v1/messaging/sms/sendlog", methods: ["GET"], description: "SMS send log", accessLevel: "merchant" },
155 { path: "/api/v1/messaging/email/sendlog", methods: ["GET"], description: "Email send log", accessLevel: "merchant" },
156
157 // Dashboard & CWA - Mixed Access
158 { path: "/api/dashboard", methods: ["GET"], description: "Dashboard summary data", accessLevel: "pos" },
159 { path: "/api/pos/config", methods: ["GET"], description: "POS configuration", accessLevel: "merchant" },
160 { path: "/api/cwa/status", methods: ["GET"], description: "CWA status", accessLevel: "merchant" },
161 { path: "/api/cwa/locations", methods: ["GET"], description: "CWA locations", accessLevel: "merchant" },
162 { path: "/api/cwa/stats/today", methods: ["GET"], description: "CWA today's stats", accessLevel: "merchant" },
163 { path: "/api/cwa/sales/totals", methods: ["GET"], description: "CWA sales totals", accessLevel: "pos" },
164 { path: "/api/printers-cartridges", methods: ["GET"], description: "Printer cartridge compatibility", accessLevel: "pos" },
165 ];
166
167 // FD1/FD3 Protocol Routes (Modern) - UNAVAILABLE (403 for both POS and Merchant)
168 const fd1Routes: ApiRoute[] = [
169 // Products
170 { path: "/api/v1/fd1/products", methods: ["GET"], description: "⚫ NOT AVAILABLE - List/search products using FD1 protocol with named fields", accessLevel: "merchant" },
171 { path: "/api/v1/fd1/products/[id]", methods: ["GET"], description: "⚫ NOT AVAILABLE - Get single product by ID with FD1 (named fields, no f-codes)", accessLevel: "merchant" },
172
173 // Sales
174 { path: "/api/v1/fd1/sales", methods: ["GET", "POST"], description: "⚫ NOT AVAILABLE - List sales or create new sale using FD1 protocol", accessLevel: "merchant" },
175 { path: "/api/v1/fd1/sales/[id]", methods: ["GET"], description: "⚫ NOT AVAILABLE - Get sale details with line items using FD1", accessLevel: "merchant" },
176
177 // Customers
178 { path: "/api/v1/fd1/customers", methods: ["GET"], description: "⚫ NOT AVAILABLE - List/search customers using FD1 protocol", accessLevel: "merchant" },
179 { path: "/api/v1/fd1/customers/[id]", methods: ["GET"], description: "⚫ NOT AVAILABLE - Get single customer by ID with FD1", accessLevel: "merchant" },
180 ];
181
182 // OpenAPI2 Routes - Mixed Access
183 const openapiRoutes: ApiRoute[] = [
184 // Products - POS Accessible
185 { path: "/api/v1/openapi/products", methods: ["GET", "POST"], description: "Products catalog (OpenAPI /Products)", accessLevel: "pos" },
186 { path: "/api/v1/openapi/products/[id]", methods: ["GET", "PUT", "PATCH", "DELETE"], description: "Individual product operations", accessLevel: "pos" },
187 { path: "/api/v1/openapi/products/[id]/writeoff", methods: ["POST"], description: "Write off product stock", accessLevel: "merchant" },
188 { path: "/api/v1/openapi/products-search", methods: ["GET"], description: "Search products (OpenAPI /Products with filters)", accessLevel: "merchant" },
189 { path: "/api/v1/openapi/barcodes", methods: ["GET"], description: "Barcode lookups (OpenAPI /Barcodes)", accessLevel: "merchant" },
190
191 // Customers & Sales - POS Accessible
192 { path: "/api/v1/openapi/customers", methods: ["GET", "POST"], description: "Customers (OpenAPI /Customers)", accessLevel: "pos" },
193 { path: "/api/v1/openapi/sales", methods: ["GET"], description: "Sales transactions (OpenAPI /Sales)", accessLevel: "pos" },
194
195 // Suppliers - Merchant Only
196 { path: "/api/v1/openapi/suppliers", methods: ["GET", "POST"], description: "Suppliers (OpenAPI /Suppliers)", accessLevel: "merchant" },
197 { path: "/api/v1/openapi/suppliers/[id]", methods: ["GET", "PUT", "DELETE"], description: "Individual supplier operations", accessLevel: "merchant" },
198 { path: "/api/v1/openapi/supplier-part-codes", methods: ["GET"], description: "Supplier part code mappings", accessLevel: "merchant" },
199 { path: "/api/v1/openapi/supplier-marketing-messages", methods: ["GET"], description: "Supplier marketing content", accessLevel: "merchant" },
200
201 // Organization - Merchant Only
202 { path: "/api/v1/openapi/locations", methods: ["GET"], description: "Store locations (OpenAPI /Location)", accessLevel: "merchant" },
203 { path: "/api/v1/openapi/departments", methods: ["GET", "POST"], description: "Departments (OpenAPI /Departments)", accessLevel: "merchant" },
204 { path: "/api/v1/openapi/departments/[id]", methods: ["GET", "PUT", "DELETE"], description: "Individual department operations", accessLevel: "merchant" },
205 { path: "/api/v1/openapi/webgroups", methods: ["GET"], description: "Web category groups", accessLevel: "merchant" },
206 { path: "/api/v1/openapi/pricebook-specials", methods: ["GET"], description: "Special pricing and promotions", accessLevel: "merchant" },
207 { path: "/api/v1/openapi/shelf-visits", methods: ["GET"], description: "Shelf visit tracking (OpenAPI /ShelfVisits)", accessLevel: "merchant" },
208
209 // Inventory - Mostly Merchant Only
210 { path: "/api/v1/openapi/stocktakes", methods: ["GET", "POST"], description: "Stocktakes (OpenAPI /Stocktakes)", accessLevel: "merchant" },
211 { path: "/api/v1/openapi/stock2/primitive", methods: ["GET"], description: "Stock primitive data", accessLevel: "merchant" },
212 { path: "/api/v1/openapi/stock2/count", methods: ["GET", "POST"], description: "Stock count operations", accessLevel: "merchant" },
213 { path: "/api/v1/openapi/stock2/count/[key]", methods: ["GET", "PUT"], description: "Individual stock count", accessLevel: "merchant" },
214 { path: "/api/v1/openapi/stock2/count/control/[action]", methods: ["POST"], description: "Stock count control actions", accessLevel: "merchant" },
215 { path: "/api/v1/openapi/stock2/count-summary", methods: ["GET"], description: "Stock count summaries", accessLevel: "merchant" },
216 { path: "/api/v1/openapi/stock2/movements", methods: ["GET", "POST"], description: "Stock movement tracking", accessLevel: "merchant" },
217 { path: "/api/v1/openapi/stock-transfers/[id]", methods: ["GET", "PUT"], description: "Stock transfer operations", accessLevel: "merchant" },
218 { path: "/api/v1/openapi/stock-transfers/[id]/receive", methods: ["POST"], description: "Receive stock transfer", accessLevel: "merchant" },
219 { path: "/api/v1/openapi/reorder-levels", methods: ["GET", "POST"], description: "Product reorder levels", accessLevel: "merchant" },
220 { path: "/api/v1/openapi/reorder-levels/[locid]/[pid]", methods: ["GET", "PUT"], description: "Specific reorder level", accessLevel: "merchant" },
221
222 // Purchase Orders - Merchant Only
223 { path: "/api/v1/openapi/purchase-orders", methods: ["GET", "POST"], description: "Purchase orders (OpenAPI /PurchaseOrders)", accessLevel: "merchant" },
224 { path: "/api/v1/openapi/purchase-order/parse", methods: ["POST"], description: "Parse purchase order documents", accessLevel: "merchant" },
225 ];
226
227 const allRoutes = [
228 ...elinkRoutes.map(r => ({ ...r, type: "elink" as const })),
229 ...fd1Routes.map(r => ({ ...r, type: "fd1" as const })),
230 ...openapiRoutes.map(r => ({ ...r, type: "openapi" as const }))
231 ];
232
233 const filteredRoutes = allRoutes.filter(route => {
234 const matchesSearch = !searchTerm ||
235 route.path.toLowerCase().includes(searchTerm.toLowerCase()) ||
236 route.description.toLowerCase().includes(searchTerm.toLowerCase());
237
238 const matchesType = selectedType === "all" || route.type === selectedType;
239 const matchesAccess = selectedAccess === "all" || route.accessLevel === selectedAccess;
240
241 return matchesSearch && matchesType && matchesAccess;
242 });
243
244 const posCount = allRoutes.filter(r => r.accessLevel === "pos").length;
245 const merchantCount = allRoutes.filter(r => r.accessLevel === "merchant").length;
246 const workingCount = allRoutes.filter(r => getTestStatus(r.path) === "success").length;
247
248 return (
249 <div className="min-h-screen bg-bg print:bg-white">
250 {/* Header - Hidden in print */}
251 <div className="bg-gradient-to-r from-brand to-brand2 text-white py-12 px-6 print:hidden">
252 <div className="max-w-7xl mx-auto">
253 <h1 className="text-4xl font-bold mb-3 flex items-center gap-3">
254 <Icon name="api" size={40} />
255 API Documentation
256 </h1>
257 <p className="text-xl text-white/90">Complete reference for all Everyday POS API routes with access level classifications</p>
258 <div className="mt-4 flex gap-4 text-sm flex-wrap items-center">
259 <div className="bg-white/20 px-4 py-2 rounded-lg">
260 <span className="font-semibold">{allRoutes.length}</span> Total Routes
261 </div>
262 <div className="bg-green-500/30 px-4 py-2 rounded-lg border border-green-400/30">
263 <span className="font-semibold">{posCount}</span> POS Accessible
264 </div>
265 <div className="bg-red-500/30 px-4 py-2 rounded-lg border border-red-400/30">
266 <span className="font-semibold">{merchantCount}</span> Merchant Only
267 </div>
268 <div className="bg-white/20 px-4 py-2 rounded-lg">
269 <span className="font-semibold">{elinkRoutes.length}</span> eLink/BUCK
270 </div>
271 <div className="bg-white/20 px-4 py-2 rounded-lg">
272 <span className="font-semibold">{fd1Routes.length}</span> FD1/FD3
273 </div>
274 <div className="bg-white/20 px-4 py-2 rounded-lg">
275 <span className="font-semibold">{openapiRoutes.length}</span> OpenAPI
276 </div>
277 <button
278 onClick={() => window.print()}
279 className="ml-auto bg-white text-brand px-6 py-2 rounded-lg font-semibold hover:bg-white/90 transition-colors flex items-center gap-2 shadow-lg"
280 >
281 <Icon name="print" size={20} />
282 Print to PDF
283 </button>
284 </div>
285 </div>
286 </div>
287
288 <div className="max-w-7xl mx-auto py-8 px-6 print:py-2 print:px-4">
289 {/* Print-only title */}
290 <div className="hidden print:block mb-6">
291 <h1 className="text-3xl font-bold mb-2">Everyday POS API Documentation</h1>
292 <p className="text-sm text-gray-700 mb-1">Complete reference for all API routes with access level classifications</p>
293 <p className="text-xs text-gray-600">Total Routes: {allRoutes.length} | Working: {workingCount} | POS Access: {posCount} | Merchant Access: {merchantCount}</p>
294 <hr className="my-4 border-black" />
295 </div>
296
297 {/* Info Boxes - Hidden in print */}
298 <div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8 print:hidden">
299 <div className="bg-success/10 border-l-4 border-success p-6 rounded-lg">
300 <h3 className="text-lg font-bold text-text mb-2 flex items-center gap-2">
301 <Icon name="check_circle" size={24} className="text-success" />
302 POS Store Access
303 </h3>
304 <p className="text-sm text-text mb-3">
305 Available to all stores including agency/franchise stores. Basic operations for day-to-day POS use.
306 </p>
307 <ul className="text-xs text-muted space-y-1">
308 <li>• {posCount} endpoints available</li>
309 <li>• Product & customer lookups</li>
310 <li>• Basic reporting</li>
311 <li>• Stock level monitoring</li>
312 </ul>
313 </div>
314
315 <div className="bg-info/10 border-l-4 border-info p-6 rounded-lg">
316 <h3 className="text-lg font-bold text-text mb-2 flex items-center gap-2">
317 <Icon name="admin_panel_settings" size={24} className="text-info" />
318 Merchant Store Only
319 </h3>
320 <p className="text-sm text-text mb-3">
321 Requires merchant/master store permissions. Administrative and advanced features restricted for security.
322 </p>
323 <ul className="text-xs text-muted space-y-1">
324 <li>• {merchantCount} restricted endpoints</li>
325 <li>• Detailed sales analytics</li>
326 <li>• Staff management</li>
327 <li>• Advanced inventory ops</li>
328 </ul>
329 </div>
330
331 <div className="bg-info/10 border-l-4 border-info p-6 rounded-lg">
332 <h3 className="text-lg font-bold text-text mb-2 flex items-center gap-2">
333 <Icon name="storage" size={24} className="text-info" />
334 eLink/BUCK API
335 </h3>
336 <p className="text-sm text-text mb-3">
337 Legacy Fieldpine protocol using numbered parameters (3, 7, 8, 9) and f-code field names (f100, f101).
338 </p>
339 <ul className="text-xs text-muted space-y-1">
340 <li>• Returns: JSON with DATS/APPD arrays</li>
341 <li>• Fields: Numeric identifiers (f100 = ID)</li>
342 <li>• Endpoint format: buck?3=retailmax.elink.*</li>
343 <li>• Fast, optimized for POS operations</li>
344 </ul>
345 </div>
346
347 <div className="bg-brand/10 border-l-4 border-brand p-6 rounded-lg">
348 <h3 className="text-lg font-bold text-text mb-2 flex items-center gap-2">
349 <Icon name="rocket_launch" size={24} className="text-brand" />
350 FD1 & OpenAPI
351 </h3>
352 <p className="text-sm text-text mb-3">
353 Modern protocols with named fields. FD1 is currently unavailable, OpenAPI has mixed access.
354 </p>
355 <ul className="text-xs text-muted space-y-1">
356 <li>• <strong>FD1:</strong> ⚫ Disabled (403 for all stores)</li>
357 <li>• <strong>OpenAPI:</strong> Mixed access levels</li>
358 <li>• Named fields (no f-codes)</li>
359 <li>• Standard REST methods</li>
360 </ul>
361 </div>
362 </div>
363
364 {/* Test Results Banner - Hidden in print */}
365 <div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4 mb-8 print:hidden">
366 <div className="flex items-start gap-3">
367 <Icon name="info" size={24} className="text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
368 <div>
369 <h3 className="font-bold text-blue-900 dark:text-blue-100 mb-2">Access Levels Based on Actual Testing</h3>
370 <p className="text-sm text-blue-800 dark:text-blue-200 mb-3">
371 Classifications verified with real API tests on 2026-01-20 using both POS store (Murwillumbah) and merchant portal (IIG) credentials.
372 </p>
373 <div className="grid grid-cols-2 gap-4 text-xs">
374 <div>
375 <div className="font-semibold text-blue-900 dark:text-blue-100 mb-1">POS Store (Murwillumbah)</div>
376 <ul className="text-blue-700 dark:text-blue-300 space-y-0.5">
377 <li>✅ 23/110 endpoints working (21%)</li>
378 <li>• Core products & locations</li>
379 <li>• Basic reporting</li>
380 <li>• Stock monitoring</li>
381 </ul>
382 </div>
383 <div>
384 <div className="font-semibold text-blue-900 dark:text-blue-100 mb-1">Merchant Portal (IIG)</div>
385 <ul className="text-blue-700 dark:text-blue-300 space-y-0.5">
386 <li>✅ 76/110 endpoints working (69%)</li>
387 <li>• +53 additional endpoints</li>
388 <li>• Sales, staff, analytics</li>
389 <li>• Suppliers, departments, config</li>
390 </ul>
391 </div>
392 </div>
393 <div className="mt-3 pt-3 border-t border-blue-200 dark:border-blue-800">
394 <p className="text-xs text-blue-700 dark:text-blue-300">
395 <strong>Known Issues:</strong> FD1 protocol disabled (6 endpoints return 403 for all). Stats endpoints partially unavailable (4 return 404).
396 See <a href="/docs/API_ACCESS_LEVELS.md" className="underline hover:text-blue-900 dark:hover:text-blue-100">API_ACCESS_LEVELS.md</a> for details.
397 </p>
398 </div>
399 </div>
400 </div>
401 </div>
402 </div>
403
404 {/* Filters - Hidden in print */}
405 <div className="bg-surface rounded-lg shadow-sm p-6 mb-6 print:hidden">
406 <div className="flex flex-col gap-4">
407 <div className="flex-1 w-full">
408 <input
409 type="text"
410 placeholder="Search routes by path or description..."
411 value={searchTerm}
412 onChange={(e) => setSearchTerm(e.target.value)}
413 className="w-full px-4 py-2 border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-brand bg-surface text-text"
414 />
415 </div>
416
417 {/* Protocol Type Filter */}
418 <div className="flex flex-col sm:flex-row gap-4">
419 <div className="flex-1">
420 <label className="text-xs font-semibold text-muted uppercase tracking-wide mb-2 block">Protocol Type</label>
421 <div className="flex gap-2 flex-wrap">
422 <button
423 onClick={() => setSelectedType("all")}
424 className={`px-4 py-2 rounded-lg font-medium transition-colors ${
425 selectedType === "all"
426 ? "bg-brand text-white"
427 : "bg-surface-2 text-text hover:bg-surface-2/80"
428 }`}
429 >
430 All ({allRoutes.length})
431 </button>
432 <button
433 onClick={() => setSelectedType("elink")}
434 className={`px-4 py-2 rounded-lg font-medium transition-colors ${
435 selectedType === "elink"
436 ? "bg-info text-white"
437 : "bg-surface-2 text-text hover:bg-surface-2/80"
438 }`}
439 >
440 eLink ({elinkRoutes.length})
441 </button>
442 <button
443 onClick={() => setSelectedType("fd1")}
444 className={`px-4 py-2 rounded-lg font-medium transition-colors ${
445 selectedType === "fd1"
446 ? "bg-brand text-white"
447 : "bg-surface-2 text-text hover:bg-surface-2/80"
448 }`}
449 >
450 FD1 ({fd1Routes.length})
451 </button>
452 <button
453 onClick={() => setSelectedType("openapi")}
454 className={`px-4 py-2 rounded-lg font-medium transition-colors ${
455 selectedType === "openapi"
456 ? "bg-success text-white"
457 : "bg-surface-2 text-text hover:bg-surface-2/80"
458 }`}
459 >
460 OpenAPI ({openapiRoutes.length})
461 </button>
462 </div>
463 </div>
464
465 {/* Access Level Filter */}
466 <div className="flex-1">
467 <label className="text-xs font-semibold text-muted uppercase tracking-wide mb-2 block">Access Level</label>
468 <div className="flex gap-2 flex-wrap">
469 <button
470 onClick={() => setSelectedAccess("all")}
471 className={`px-4 py-2 rounded-lg font-medium transition-colors ${
472 selectedAccess === "all"
473 ? "bg-brand text-white"
474 : "bg-surface-2 text-text hover:bg-surface-2/80"
475 }`}
476 >
477 All Levels
478 </button>
479 <button
480 onClick={() => setSelectedAccess("pos")}
481 className={`px-4 py-2 rounded-lg font-medium transition-colors border flex items-center gap-2 ${
482 selectedAccess === "pos"
483 ? "bg-success text-white border-success"
484 : "bg-surface-2 text-text hover:bg-surface-2/80 border-success/30"
485 }`}
486 >
487 <Icon name="check_circle" size={18} className={selectedAccess === "pos" ? "text-white" : "text-success"} />
488 POS ({posCount})
489 </button>
490 <button
491 onClick={() => setSelectedAccess("merchant")}
492 className={`px-4 py-2 rounded-lg font-medium transition-colors border flex items-center gap-2 ${
493 selectedAccess === "merchant"
494 ? "bg-info text-white border-info"
495 : "bg-surface-2 text-text hover:bg-surface-2/80 border-info/30"
496 }`}
497 >
498 <Icon name="admin_panel_settings" size={18} className={selectedAccess === "merchant" ? "text-white" : "text-info"} />
499 Merchant ({merchantCount})
500 </button>
501 </div>
502 </div>
503 </div>
504 </div>
505
506 {searchTerm && (
507 <div className="mt-3 text-sm text-muted">
508 Found {filteredRoutes.length} route(s) matching "{searchTerm}"
509 </div>
510 )}
511 </div>
512
513 {/* Routes List */}
514 <div className="space-y-6 print:space-y-4">
515 {selectedType === "all" || selectedType === "elink" ? (
516 <div className="bg-surface rounded-lg shadow-sm overflow-hidden print:shadow-none print:border print:border-black page-break-avoid">
517 <div className="bg-info text-white px-6 py-4 print:bg-white print:text-black print:py-2 print:border-b-2 print:border-black">
518 <h2 className="text-xl font-bold flex items-center gap-2 print:text-lg">
519 <Icon name="storage" size={24} className="print:hidden" />
520 eLink/BUCK API Routes ({elinkRoutes.length})
521 </h2>
522 </div>
523 <div className="divide-y divide-border print:divide-black">
524 {filteredRoutes
525 .filter(r => r.type === "elink")
526 .map((route, idx) => (
527 <div key={idx} className="p-5 hover:bg-surface-2 transition-colors page-break-avoid print:p-3 print:hover:bg-white">
528 <div className="flex items-start justify-between gap-4 mb-2 print:mb-1">
529 <div className="flex-1 flex items-center gap-2 flex-wrap print:gap-1">
530 <Icon name="route" size={16} className="text-info flex-shrink-0 print:hidden" />
531 <code className="text-sm font-mono bg-info/10 text-info px-3 py-1 rounded print:bg-white print:text-black print:px-1 print:py-0 print:text-xs">
532 {route.path}
533 </code>
534 {route.accessLevel === "pos" && (
535 <span className="px-2 py-1 text-xs font-semibold rounded bg-success/20 text-success border border-success/30 flex items-center gap-1 print:bg-white print:text-black print:border-black print:px-1 print:py-0">
536 <Icon name="check_circle" size={14} className="print:hidden" />
537 POS
538 </span>
539 )}
540 {route.accessLevel === "merchant" && (
541 <span className="px-2 py-1 text-xs font-semibold rounded bg-info/20 text-info border border-info/30 flex items-center gap-1 print:bg-white print:text-black print:border-black print:px-1 print:py-0">
542 <Icon name="admin_panel_settings" size={14} className="print:hidden" />
543 Merchant
544 </span>
545 )}
546 {(() => {
547 const status = getTestStatus(route.path);
548 if (status === "success") {
549 return (
550 <span className="px-2 py-1 text-xs font-semibold rounded bg-success/20 text-success border border-success/30 flex items-center gap-1 print:hidden">
551 <Icon name="check_circle" size={14} />
552 Tested ✓
553 </span>
554 );
555 } else if (status === "failed") {
556 return (
557 <span className="px-2 py-1 text-xs font-semibold rounded bg-danger/20 text-danger border border-danger/30 flex items-center gap-1 print:hidden">
558 <Icon name="error" size={14} />
559 Failed
560 </span>
561 );
562 }
563 return null;
564 })()}
565 </div>
566 <div className="flex gap-2 print:gap-1">
567 {route.methods.map(method => (
568 <span
569 key={method}
570 className={`px-2 py-1 text-xs font-semibold rounded print:px-1 print:py-0 print:text-black print:bg-white print:border print:border-black ${
571 method === "GET"
572 ? "bg-success/20 text-success"
573 : method === "POST"
574 ? "bg-info/20 text-info"
575 : method === "PUT" || method === "PATCH"
576 ? "bg-warn/20 text-warn"
577 : "bg-danger/20 text-danger"
578 }`}
579 >
580 {method}
581 </span>
582 ))}
583 </div>
584 </div>
585 <p className="text-sm text-muted ml-1 print:text-xs print:text-black print:ml-0">{route.description}</p>
586 </div>
587 ))}
588 {filteredRoutes.filter(r => r.type === "elink").length === 0 && (
589 <div className="p-8 text-center text-muted print:hidden">
590 No eLink routes match your search
591 </div>
592 )}
593 </div>
594 </div>
595 ) : null}
596
597 {selectedType === "all" || selectedType === "fd1" ? (
598 <div className="bg-surface rounded-lg shadow-sm overflow-hidden border-2 border-gray-400 print:shadow-none print:border print:border-black page-break-avoid">
599 <div className="bg-gray-700 text-white px-6 py-4 print:bg-white print:text-black print:py-2 print:border-b-2 print:border-black">
600 <h2 className="text-xl font-bold flex items-center gap-2 print:text-lg">
601 <Icon name="block" size={24} className="print:hidden" />
602 FD1/FD3 Protocol Routes ({fd1Routes.length})
603 </h2>
604 <p className="text-sm text-white/90 mt-1 flex items-center gap-2 print:text-xs print:text-black print:mt-0">
605 <Icon name="block" size={16} className="print:hidden" />
606 <strong>NOT AVAILABLE:</strong> All FD1 endpoints return 403 Forbidden for both POS and merchant stores (protocol disabled)
607 </p>
608 </div>
609 <div className="divide-y divide-border bg-gray-50 dark:bg-gray-900 print:bg-white print:divide-black">
610 {filteredRoutes
611 .filter(r => r.type === "fd1")
612 .map((route, idx) => (
613 <div key={idx} className="p-5 hover:bg-surface-2 transition-colors opacity-60 page-break-avoid print:p-3 print:hover:bg-white print:opacity-100">
614 <div className="flex items-start justify-between gap-4 mb-2 print:mb-1">
615 <div className="flex-1 flex items-center gap-2 print:gap-1">
616 <Icon name="route" size={16} className="text-gray-500 flex-shrink-0 print:hidden" />
617 <code className="text-sm font-mono bg-gray-200 dark:bg-gray-800 text-gray-700 dark:text-gray-300 px-3 py-1 rounded line-through print:bg-white print:text-black print:px-1 print:py-0 print:text-xs">
618 {route.path}
619 </code>
620 <span className="px-2 py-1 text-xs font-semibold rounded bg-gray-200 text-gray-600 border border-gray-400 flex items-center gap-1">
621 <Icon name="block" size={14} />
622 DISABLED
623 </span>
624 </div>
625 <div className="flex gap-2">
626 {route.methods.map(method => (
627 <span
628 key={method}
629 className={`px-2 py-1 text-xs font-semibold rounded ${
630 method === "GET"
631 ? "bg-success/20 text-success"
632 : method === "POST"
633 ? "bg-info/20 text-info"
634 : method === "PUT" || method === "PATCH"
635 ? "bg-warn/20 text-warn"
636 : "bg-danger/20 text-danger"
637 }`}
638 >
639 {method}
640 </span>
641 ))}
642 </div>
643 </div>
644 <p className="text-sm text-muted ml-1">{route.description}</p>
645 </div>
646 ))}
647 {filteredRoutes.filter(r => r.type === "fd1").length === 0 && (
648 <div className="p-8 text-center text-muted">
649 No FD1 routes match your search
650 </div>
651 )}
652 </div>
653 </div>
654 ) : null}
655
656 {selectedType === "all" || selectedType === "openapi" ? (
657 <div className="bg-surface rounded-lg shadow-sm overflow-hidden print:shadow-none print:border print:border-black page-break-avoid">
658 <div className="bg-success text-white px-6 py-4 print:bg-white print:text-black print:py-2 print:border-b-2 print:border-black">
659 <h2 className="text-xl font-bold flex items-center gap-2 print:text-lg">
660 <Icon name="cloud" size={24} className="print:hidden" />
661 OpenAPI2 Routes ({openapiRoutes.length})
662 </h2>
663 <p className="text-sm text-white/80 mt-1 print:text-xs print:text-black print:mt-0">RESTful API with mixed access levels - check each endpoint</p>
664 </div>
665 <div className="divide-y divide-border print:divide-black">
666 {filteredRoutes
667 .filter(r => r.type === "openapi")
668 .map((route, idx) => (
669 <div key={idx} className="p-5 hover:bg-surface-2 transition-colors page-break-avoid print:p-3 print:hover:bg-white">
670 <div className="flex items-start justify-between gap-4 mb-2 print:mb-1">
671 <div className="flex-1 flex items-center gap-2 flex-wrap print:gap-1">
672 <Icon name="route" size={16} className="text-success flex-shrink-0 print:hidden" />
673 <code className="text-sm font-mono bg-success/10 text-success px-3 py-1 rounded print:bg-white print:text-black print:px-1 print:py-0 print:text-xs">
674 {route.path}
675 </code>
676 {route.accessLevel === "pos" && (
677 <span className="px-2 py-1 text-xs font-semibold rounded bg-success/20 text-success border border-success/30 flex items-center gap-1 print:bg-white print:text-black print:border-black print:px-1 print:py-0">
678 <Icon name="check_circle" size={14} className="print:hidden" />
679 POS
680 </span>
681 )}
682 {route.accessLevel === "merchant" && (
683 <span className="px-2 py-1 text-xs font-semibold rounded bg-info/20 text-info border border-info/30 flex items-center gap-1 print:bg-white print:text-black print:border-black print:px-1 print:py-0">
684 <Icon name="admin_panel_settings" size={14} className="print:hidden" />
685 Merchant
686 </span>
687 )}
688 {(() => {
689 const status = getTestStatus(route.path);
690 if (status === "success") {
691 return (
692 <span className="px-2 py-1 text-xs font-semibold rounded bg-success/20 text-success border border-success/30 flex items-center gap-1 print:hidden">
693 <Icon name="check_circle" size={14} />
694 Tested ✓
695 </span>
696 );
697 } else if (status === "failed") {
698 return (
699 <span className="px-2 py-1 text-xs font-semibold rounded bg-danger/20 text-danger border border-danger/30 flex items-center gap-1 print:hidden">
700 <Icon name="error" size={14} />
701 Failed
702 </span>
703 );
704 }
705 return null;
706 })()}
707 </div>
708 <div className="flex gap-2 print:gap-1">
709 {route.methods.map(method => (
710 <span
711 key={method}
712 className={`px-2 py-1 text-xs font-semibold rounded print:px-1 print:py-0 print:text-black print:bg-white print:border print:border-black ${
713 method === "GET"
714 ? "bg-success/20 text-success"
715 : method === "POST"
716 ? "bg-info/20 text-info"
717 : method === "PUT" || method === "PATCH"
718 ? "bg-warn/20 text-warn"
719 : "bg-danger/20 text-danger"
720 }`}
721 >
722 {method}
723 </span>
724 ))}
725 </div>
726 </div>
727 <p className="text-sm text-muted ml-1 print:text-xs print:text-black print:ml-0">{route.description}</p>
728 </div>
729 ))}
730 {filteredRoutes.filter(r => r.type === "openapi").length === 0 && (
731 <div className="p-8 text-center text-muted print:hidden">
732 No OpenAPI routes match your search
733 </div>
734 )}
735 </div>
736 </div>
737 ) : null}
738 </div>
739
740 {/* Quick Reference - Hidden in print */}
741 <div className="mt-8 bg-surface rounded-lg shadow-sm p-6 print:hidden">
742 <h3 className="text-lg font-bold text-text mb-4 flex items-center gap-2">
743 <Icon name="menu_book" size={24} />
744 Quick Reference
745 </h3>
746
747 <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
748 <div>
749 <h4 className="font-semibold text-text mb-2">Authentication</h4>
750 <p className="text-sm text-muted mb-2">
751 All routes require authentication via HTTP-only cookie:
752 </p>
753 <code className="text-xs bg-surface-2 text-text px-2 py-1 rounded block">
754 Cookie: FieldpineApiKey=your_api_key
755 </code>
756 </div>
757
758 <div>
759 <h4 className="font-semibold text-text mb-2">Base URL</h4>
760 <p className="text-sm text-muted mb-2">
761 All API routes are relative to:
762 </p>
763 <code className="text-xs bg-surface-2 text-text px-2 py-1 rounded block">
764 http://localhost:3000
765 </code>
766 </div>
767
768 <div>
769 <h4 className="font-semibold text-text mb-2">Response Format</h4>
770 <pre className="text-xs bg-surface-2 text-text p-3 rounded overflow-x-auto">
771{`{
772 "success": true,
773 "data": { /* result */ }
774}`}
775 </pre>
776 </div>
777
778 <div>
779 <h4 className="font-semibold text-text mb-2">Error Format</h4>
780 <pre className="text-xs bg-surface-2 text-text p-3 rounded overflow-x-auto">
781{`{
782 "success": false,
783 "error": "Error message"
784}`}
785 </pre>
786 </div>
787 </div>
788
789 <div className="mt-6 p-4 bg-warn/10 border-l-4 border-warn rounded">
790 <p className="text-sm text-text flex items-start gap-2">
791 <Icon name="tips_and_updates" size={20} className="text-warn flex-shrink-0 mt-0.5" />
792 <span>
793 <strong>Pro Tip:</strong> Use <code className="bg-warn/20 text-warn px-2 py-1 rounded">apiClient</code> from{" "}
794 <code className="bg-warn/20 text-warn px-2 py-1 rounded">{"/lib/client/apiClient.ts"}</code> instead of calling these routes directly.
795 </span>
796 </p>
797 </div>
798
799 <div className="mt-4 p-4 bg-info/10 border-l-4 border-info rounded">
800 <p className="text-sm text-text flex items-start gap-2">
801 <Icon name="info" size={20} className="text-info flex-shrink-0 mt-0.5" />
802 <span>
803 <strong>Access Levels:</strong> These classifications are based on actual testing with agency/POS store credentials.
804 POS stores receive 403 Forbidden errors when accessing merchant-only endpoints.
805 For detailed test results, see{" "}
806 <code className="bg-info/20 text-info px-2 py-1 rounded">{"/docs/API_ACCESS_LEVELS.md"}</code>
807 </span>
808 </p>
809 </div>
810
811 {testDataLoaded && (
812 <div className="mt-4 p-4 bg-success/10 border-l-4 border-success rounded">
813 <p className="text-sm text-text flex items-start gap-2">
814 <Icon name="verified" size={20} className="text-success flex-shrink-0 mt-0.5" />
815 <span>
816 <strong>Live Test Results:</strong> Endpoints marked with "Tested ✓" have been verified working in production.
817 Test data loaded from Murwillumbah store ({testResults.length} endpoints tested).
818 </span>
819 </p>
820 </div>
821 )}
822 </div>
823 </div>
824 );
825}