3import React, { useEffect, useMemo, useState } from "react";
4import { apiClient } from "@/lib/client/apiClient";
11 paymentHistory?: string;
12 lastStatement?: string;
15export default function AccountAdjustmentPage() {
16 const [searchTerm, setSearchTerm] = useState("");
17 const [searchResults, setSearchResults] = useState<Account[]>([]);
18 const [searchLoading, setSearchLoading] = useState(false);
19 const [selectedAccount, setSelectedAccount] = useState<Account | null>(null);
21 const [amount, setAmount] = useState<string>("");
22 const [adjustmentDate, setAdjustmentDate] = useState<string>(() => {
24 return d.toISOString().slice(0, 10);
26 const [comments, setComments] = useState<string>("Adjustment");
28 const [saving, setSaving] = useState(false);
29 const [statusMessage, setStatusMessage] = useState<string>("");
30 const [statusVariant, setStatusVariant] = useState<"success" | "error" | "info" | "">("");
33 if (searchTerm.trim().length < 2) {
38 const handle = setTimeout(() => {
39 const doSearch = async () => {
40 setSearchLoading(true);
42 // Search accounts via API
43 const response = await apiClient.request(`/v1/buck/accounts?search=${encodeURIComponent(searchTerm.trim())}&limit=10`);
44 if (response?.data?.DATS) {
45 const mapped: Account[] = response.data.DATS.map((item: any) => ({
46 id: Number(item.f100),
48 balance: Number(item.f114) || 0,
50 paymentHistory: item.f1105,
51 lastStatement: item.f1106,
53 setSearchResults(mapped);
56 console.error("Account search failed", err);
58 setSearchLoading(false);
65 return () => clearTimeout(handle);
68 const formatCurrency = (value: number) => {
69 return new Intl.NumberFormat("en-US", {
72 minimumFractionDigits: 2,
76 const projectedBalance = useMemo(() => {
77 if (!selectedAccount) return null;
78 const delta = Number(amount);
79 if (Number.isNaN(delta)) return selectedAccount.balance;
80 return selectedAccount.balance + delta;
81 }, [selectedAccount, amount]);
83 const canSubmit = useMemo(() => {
84 return !!selectedAccount && Number(amount) !== 0 && !Number.isNaN(Number(amount)) && !saving;
85 }, [selectedAccount, amount, saving]);
87 const handleSelectAccount = (account: Account) => {
88 setSelectedAccount(account);
92 const handleSubmit = async () => {
93 if (!selectedAccount) {
94 setStatusMessage("Please select an account");
95 setStatusVariant("error");
99 const delta = Number(amount);
100 if (Number.isNaN(delta) || delta === 0) {
101 setStatusMessage("Enter an adjustment amount (positive or negative, not zero)");
102 setStatusVariant("error");
107 setStatusMessage("Saving adjustment...");
108 setStatusVariant("info");
112 "<f8_s>retailmax.elink.account.request</f8_s>",
114 `<f100_E>${selectedAccount.id}</f100_E>`,
115 "<f101_E>121</f101_E>",
116 `<f151_s>${amount}</f151_s>`,
117 `<f152_s>${adjustmentDate}</f152_s>`,
118 comments ? `<f153_s>${comments}</f153_s>` : "",
123 const response = await fetch("/DATI", {
126 "Content-Type": "application/xml",
127 Accept: "application/json",
129 credentials: "include",
134 throw new Error(`HTTP ${response.status}`);
137 setStatusMessage("Adjustment saved successfully.");
138 setStatusVariant("success");
141 console.error("Failed to save adjustment", err);
142 setStatusMessage("Adjustment failed. Please try again.");
143 setStatusVariant("error");
149 <div className="p-6 max-w-6xl mx-auto space-y-6">
150 <div className="mb-2">
151 <h1 className="text-3xl font-bold text-text mb-2">Record Account Adjustment</h1>
152 <p className="text-muted text-sm">
153 Apply an adjustment to an account balance. Positive amounts increase the balance owing; negative amounts decrease it.
159 className={`rounded-lg px-4 py-3 text-sm border ${
160 statusVariant === "success"
161 ? "bg-green-50 border-green-200 text-green-800"
162 : statusVariant === "error"
163 ? "bg-red-50 border-red-200 text-red-800"
164 : "bg-info/10 border-info/30 text-info"
171 <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
172 <div className="lg:col-span-2 space-y-4">
173 <div className="bg-surface rounded-lg shadow p-4">
174 <h2 className="text-lg font-semibold text-text mb-3">Select Account</h2>
175 <div className="flex items-center gap-3">
179 onChange={(e) => setSearchTerm(e.target.value)}
180 placeholder="Search account by name"
181 className="w-full px-3 py-2 border border-border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-green-500"
183 {searchLoading && <span className="text-sm text-muted">Searching...</span>}
185 {searchResults.length > 0 && (
186 <div className="mt-3 border border-border rounded-md divide-y max-h-64 overflow-y-auto">
187 {searchResults.map((acc) => (
190 onClick={() => handleSelectAccount(acc)}
191 className={`w-full text-left px-3 py-2 hover:bg-surface-2 flex justify-between items-center ${
192 selectedAccount?.id === acc.id ? "bg-green-50" : ""
196 <p className="text-sm font-medium text-text">{acc.name}</p>
197 <p className="text-xs text-muted">Id: {acc.id}</p>
199 <div className="text-sm font-semibold text-text">{formatCurrency(acc.balance)}</div>
206 <div className="bg-surface rounded-lg shadow p-4 space-y-4">
207 <h2 className="text-lg font-semibold text-text">Adjustment Details</h2>
209 <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
211 <label className="block text-sm font-medium text-text mb-1">Amount</label>
216 onChange={(e) => setAmount(e.target.value)}
217 className="w-full px-3 py-2 border border-border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-green-500"
218 placeholder="Use positive or negative values"
223 <label className="block text-sm font-medium text-text mb-1">Date</label>
226 value={adjustmentDate}
227 onChange={(e) => setAdjustmentDate(e.target.value)}
228 className="w-full px-3 py-2 border border-border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-green-500"
232 <div className="md:col-span-2">
233 <label className="block text-sm font-medium text-text mb-1">Comments (shown to account holder)</label>
237 onChange={(e) => setComments(e.target.value)}
238 className="w-full px-3 py-2 border border-border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-green-500"
239 placeholder="Adjustment description"
244 <div className="flex gap-3 pt-2">
246 onClick={handleSubmit}
247 disabled={!canSubmit}
248 className="px-5 py-2 bg-[#00543b] text-white rounded-md font-semibold hover:bg-[#003d2b] disabled:bg-surface-2 disabled:cursor-not-allowed"
250 {saving ? "Saving..." : "Save Adjustment"}
254 setSelectedAccount(null);
257 setComments("Adjustment");
258 setStatusMessage("");
259 setStatusVariant("");
261 className="px-4 py-2 border border-border rounded-md text-sm text-text hover:bg-surface-2"
269 <div className="bg-surface rounded-lg shadow p-4">
270 <h2 className="text-lg font-semibold text-text mb-3">Account Summary</h2>
272 <div className="space-y-3">
274 <p className="text-sm text-muted">Account</p>
275 <p className="text-lg font-semibold text-text">{selectedAccount.name}</p>
276 <p className="text-sm text-muted">Id: {selectedAccount.id}</p>
279 <div className="flex items-center justify-between">
280 <span className="text-sm text-muted">Current Balance</span>
281 <span className="text-lg font-bold text-text">{formatCurrency(selectedAccount.balance)}</span>
284 {typeof projectedBalance === "number" && (
285 <div className="flex items-center justify-between">
286 <span className="text-sm text-muted">New Balance (after adjustment)</span>
287 <span className="text-lg font-bold text-text">{formatCurrency(projectedBalance)}</span>
291 {selectedAccount.email && (
292 <div className="flex items-center justify-between">
293 <span className="text-sm text-muted">Email</span>
294 <a href={`mailto:${selectedAccount.email}`} className="text-sm text-[#00543b] hover:underline">
295 {selectedAccount.email}
300 {selectedAccount.lastStatement && (
301 <div className="flex items-center justify-between text-sm text-muted">
302 <span>Last Statement</span>
303 <span>{selectedAccount.lastStatement}</span>
307 {selectedAccount.paymentHistory && (
309 <p className="text-sm text-muted mb-1">Payment History</p>
311 className="text-sm text-text bg-surface-2 rounded p-3"
312 dangerouslySetInnerHTML={{ __html: selectedAccount.paymentHistory }}
318 <p className="text-sm text-muted">Select an account to see details.</p>