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 React, { useState, useEffect } from "react";
4import { apiClient } from "@/lib/client/apiClient";
5import { fieldpineApi } from "@/lib/client/fieldpineApi";
6
7interface StatementAccount {
8 Accid: number;
9 Name?: string;
10 Terms?: string;
11 TotalDebits: number;
12 TotalCredits: number;
13 BalanceDue: number;
14 BillingRun: number;
15}
16
17interface StatementRun {
18 Jobid: number;
19 MinDt: string;
20 Count: number;
21 TotalDebits?: number;
22 TotalCredits?: number;
23 TotalDue?: number;
24 accounts?: StatementAccount[];
25 loading?: boolean;
26}
27
28export default function ViewStatementsPage() {
29 const [statementRuns, setStatementRuns] = useState<StatementRun[]>([]);
30 const [loading, setLoading] = useState(true);
31
32 useEffect(() => {
33 loadStatementRuns();
34 }, []);
35
36 const loadStatementRuns = async () => {
37 setLoading(true);
38 try {
39 const response = await fieldpineApi({
40 endpoint: "/buck?3=retailmax.elink.account.statementrun&201=1",
41 });
42
43 if (response?.DATS) {
44 const runs: StatementRun[] = response.DATS.map((run: any) => ({
45 ...run,
46 accounts: [],
47 loading: false,
48 }));
49
50 setStatementRuns(runs);
51
52 // Load account details for runs with fewer than 5000 accounts
53 runs.forEach((run, index) => {
54 if (run.Count < 5000) {
55 loadAccountDetails(run.Jobid, index);
56 }
57 });
58 }
59 } catch (error) {
60 console.error("Failed to load statement runs:", error);
61 } finally {
62 setLoading(false);
63 }
64 };
65
66 const loadAccountDetails = async (jobId: number, runIndex: number) => {
67 // Mark as loading
68 setStatementRuns(prev => {
69 const updated = [...prev];
70 updated[runIndex] = { ...updated[runIndex], loading: true };
71 return updated;
72 });
73
74 try {
75 const response = await fieldpineApi({
76 endpoint: `/buck?3=retailmax.elink.account.statementrun&201=2&9=f106,0,${jobId}`,
77 });
78
79 if (response?.DATS) {
80 setStatementRuns(prev => {
81 const updated = [...prev];
82 updated[runIndex] = {
83 ...updated[runIndex],
84 accounts: response.DATS,
85 loading: false,
86 };
87 return updated;
88 });
89 }
90 } catch (error) {
91 console.error("Failed to load account details:", error);
92 setStatementRuns(prev => {
93 const updated = [...prev];
94 updated[runIndex] = { ...updated[runIndex], loading: false };
95 return updated;
96 });
97 }
98 };
99
100 const viewPdf = (billingRun: number) => {
101 window.open(
102 `/openapi/documentrequest?options=2&formatid=-108&output=pdf&billingrun=${billingRun}`,
103 "_blank"
104 );
105 };
106
107 const formatCurrency = (value: number) => {
108 return new Intl.NumberFormat("en-US", {
109 style: "currency",
110 currency: "USD",
111 minimumFractionDigits: 2,
112 }).format(value);
113 };
114
115 const formatDateTime = (dateStr: string) => {
116 if (!dateStr) return "";
117 return new Date(dateStr).toLocaleString("en-US", {
118 day: "2-digit",
119 month: "short",
120 year: "numeric",
121 hour: "2-digit",
122 minute: "2-digit",
123 second: "2-digit",
124 });
125 };
126
127 return (
128 <div className="p-6 max-w-[1600px] mx-auto">
129 {/* Header */}
130 <div className="mb-6">
131 <h1 className="text-3xl font-bold text-text mb-2">Historic Statements</h1>
132 <p className="text-sm text-muted">
133 This screen reviews statements that have been issued. It only shows statements generated
134 using the web page option. Statements created directly in PosGreen cannot be shown here.
135 </p>
136 <p className="text-sm text-muted mt-2">
137 If you need bulk access to individual statements, these are stored in the folder
138 <code className="bg-surface-2 px-2 py-1 rounded text-xs mx-1">\fieldpine\pos\statements</code>
139 for direct access by external programs.
140 </p>
141 </div>
142
143 {/* Loading State */}
144 {loading && (
145 <div className="text-center py-12">
146 <div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
147 <p className="mt-4 text-muted">Loading statement runs...</p>
148 </div>
149 )}
150
151 {/* Statement Runs */}
152 {!loading && statementRuns.length === 0 && (
153 <div className="bg-surface rounded-lg shadow p-8 text-center">
154 <p className="text-muted">No statement runs found</p>
155 </div>
156 )}
157
158 {!loading && statementRuns.length > 0 && (
159 <div className="space-y-8">
160 {statementRuns.map((run, index) => (
161 <div key={run.Jobid} className="bg-surface rounded-lg shadow">
162 {/* Run Header */}
163 <div className="border-b-2 border-border p-6">
164 <h2 className="text-2xl font-bold text-text mb-2">
165 {formatDateTime(run.MinDt)}
166 </h2>
167 <p className="text-text">
168 Number of Accounts in Run: <span className="font-bold">{run.Count}</span>
169 </p>
170 </div>
171
172 {/* Account Details */}
173 <div className="p-6">
174 {run.Count >= 5000 ? (
175 <p className="text-sm text-muted italic">
176 Too many statements to show details. View Windows folder directly to see statements.
177 </p>
178 ) : run.loading ? (
179 <div className="flex items-center justify-center py-8">
180 <div className="inline-block animate-spin rounded-full h-6 w-6 border-b-2 border-gray-900 mr-3"></div>
181 <span className="text-muted">Loading account details...</span>
182 </div>
183 ) : run.accounts && run.accounts.length > 0 ? (
184 <div className="overflow-x-auto">
185 <table className="w-full">
186 <thead className="bg-surface-2 border-b border-border">
187 <tr>
188 <th className="px-4 py-3 text-left text-xs font-medium text-muted uppercase">
189 Account Id
190 </th>
191 <th className="px-4 py-3 text-left text-xs font-medium text-muted uppercase">
192 Name
193 </th>
194 <th className="px-4 py-3 text-left text-xs font-medium text-muted uppercase">
195 Terms
196 </th>
197 <th className="px-4 py-3 text-right text-xs font-medium text-muted uppercase">
198 Debits
199 </th>
200 <th className="px-4 py-3 text-right text-xs font-medium text-muted uppercase">
201 Credits
202 </th>
203 <th className="px-4 py-3 text-right text-xs font-medium text-muted uppercase">
204 Balance
205 </th>
206 <th className="px-4 py-3 text-center text-xs font-medium text-muted uppercase">
207 Options
208 </th>
209 </tr>
210 </thead>
211 <tbody className="bg-surface divide-y divide-gray-200">
212 {run.accounts.map((account) => (
213 <tr key={`${run.Jobid}-${account.Accid}`} className="hover:bg-surface-2">
214 <td className="px-4 py-3 text-sm font-medium text-text">
215 {account.Accid}
216 </td>
217 <td className="px-4 py-3 text-sm text-text">
218 {account.Name ? (
219 account.Name
220 ) : (
221 <span className="text-red-500">No Name</span>
222 )}
223 </td>
224 <td className="px-4 py-3 text-sm text-muted">
225 {account.Terms || ""}
226 </td>
227 <td className="px-4 py-3 text-sm text-right text-text">
228 {formatCurrency(account.TotalDebits)}
229 </td>
230 <td className="px-4 py-3 text-sm text-right text-text">
231 {formatCurrency(account.TotalCredits)}
232 </td>
233 <td className="px-4 py-3 text-sm text-right font-medium text-text">
234 {formatCurrency(account.BalanceDue)}
235 </td>
236 <td className="px-4 py-3 text-center">
237 <button
238 onClick={() => viewPdf(account.BillingRun)}
239 className="px-4 py-1.5 bg-[#00543b] text-white text-sm rounded hover:bg-[#003d2b] transition-colors"
240 >
241 View
242 </button>
243 </td>
244 </tr>
245 ))}
246 </tbody>
247 </table>
248 </div>
249 ) : (
250 <p className="text-sm text-muted">No accounts found in this run</p>
251 )}
252 </div>
253 </div>
254 ))}
255 </div>
256 )}
257 </div>
258 );
259}