3import React, { useState, useEffect } from 'react';
4import { Icon } from '@/contexts/IconContext';
6interface ProfileUsage {
12interface ToggleSwitchProps {
14 onChange: (checked: boolean) => void;
18const ToggleSwitch: React.FC<ToggleSwitchProps> = ({ checked, onChange, disabled = false }) => {
20 <label className="relative inline-block w-12 h-6">
24 onChange={(e) => onChange(e.target.checked)}
26 className="sr-only peer"
28 <span className={`absolute inset-0 rounded-full transition-colors duration-300 cursor-pointer
29 ${checked ? 'bg-brand' : 'bg-danger'}
30 ${disabled ? 'opacity-30 cursor-not-allowed' : ''}
31 peer-focus:ring-2 peer-focus:ring-brand`}>
32 <span className={`absolute left-1 top-1 w-4 h-4 bg-surface rounded-full transition-transform duration-300
33 ${checked ? 'translate-x-6' : 'translate-x-0'}`} />
39export default function EftposSettingsPage() {
40 const [profiles, setProfiles] = useState<ProfileUsage[]>([
41 { id: 0, name: 'No Profile', used: 0 },
42 { id: 1, name: 'Profile 1', used: 0 },
43 { id: 2, name: 'Profile 2', used: 0 },
44 { id: 3, name: 'Profile 3', used: 0 },
45 { id: 4, name: 'Profile 4', used: 0 },
46 { id: 5, name: 'Profile 5', used: 0 },
47 { id: 6, name: 'Profile 6', used: 0 },
48 { id: 7, name: 'Profile 7', used: 0 },
49 { id: 8, name: 'Profile 8', used: 0 },
53 const [productData, setProductData] = useState<Record<number, boolean>>({});
54 const [lowValueWarning, setLowValueWarning] = useState<Record<number, boolean>>({});
55 const [cashWarning, setCashWarning] = useState<Record<number, string>>({});
56 const [cashOutCopies, setCashOutCopies] = useState<Record<number, string>>({});
57 const [extendedSlip, setExtendedSlip] = useState<Record<number, boolean>>({});
58 const [versionWarning, setVersionWarning] = useState<Record<number, boolean>>({});
59 const [eftRefund, setEftRefund] = useState<Record<number, boolean>>({});
60 const [receiptInstant, setReceiptInstant] = useState<Record<number, string>>({});
61 const [receiptInline, setReceiptInline] = useState<Record<number, string>>({});
64 const [linklyUsername, setLinklyUsername] = useState('');
65 const [linklyPassword, setLinklyPassword] = useState('');
72 const loadProfileUsage = () => {
73 // API call: /BUCK?3=retailmax.elink.lane.summary&100=profileuse
74 console.log('Loading profile usage...');
75 // Mock data - would update profiles state with actual lane counts
78 const loadSettings = () => {
79 // Load all settings for all profiles
80 console.log('Loading EFTPOS settings...');
81 // Would make multiple API calls to load each setting
84 const saveSetting = (settingName: string, profile: number, value: string | boolean) => {
85 const suffix = profile === -1 ? '' : `@p${profile}`;
86 const fullSettingName = `${settingName}${suffix}`;
88 // API call: /DATI with XML payload
90 <f8_s>retailmax.elink.config.basesetting.edit</f8_s>
92 <f100_s>${fullSettingName}</f100_s>
93 <f101_s>${value}</f101_s>
97 console.log(`Saving ${fullSettingName} = ${value}`);
98 console.log('XML:', xml);
101 const ProfileCell: React.FC<{ profile: number }> = ({ profile }) => {
102 if (profile === -1) {
103 return <div className="text-xs text-muted">Default</div>;
105 const p = profiles.find(pr => pr.id === profile);
107 <div className="text-center">
108 <div className="text-xs font-medium text-text">{p?.name}</div>
109 <div className="text-xs text-muted">{p?.used} lanes</div>
115 <div className="p-6 min-h-screen bg-bg">
117 <div className="mb-6">
118 <h1 className="text-3xl font-bold text-text mb-2 flex items-center gap-2">
119 <Icon name="credit_card" size={32} />
122 <p className="text-muted">
123 Configure EFTPOS pinpads and payment terminal operation
127 <div className="max-w-[1600px] mx-auto">
129 <div className="mb-6">
130 <h1 className="text-2xl font-bold text-text mb-2">EFTPOS Pinpads and Operation</h1>
131 <p className="text-sm text-muted">
132 Configure how EFTPOS terminals operate across different lane profiles
136 {/* Profile 0 Notice */}
137 {profiles[0].used > 0 && (
138 <div className="bg-info/10 border border-info/30 rounded-lg p-4 mb-6">
139 <p className="text-sm text-text">
140 There are {profiles[0].used} lanes not assigned to a profile. They are not affected by these settings.
145 {/* Settings Table */}
146 <div className="bg-surface rounded-lg shadow overflow-x-auto">
147 <table className="w-full">
149 <tr className="border-b border-border bg-surface-2">
150 <th className="text-left p-4 font-semibold text-text min-w-[200px]">Setting</th>
151 <th className="text-center p-4 font-medium text-muted min-w-[100px]">
152 <ProfileCell profile={-1} />
154 {[1, 2, 3, 4, 5, 6, 7, 8].map(p => (
155 <th key={p} className="text-center p-4 font-medium text-muted min-w-[100px]">
156 <ProfileCell profile={p} />
159 <th className="text-center p-4 font-medium text-muted min-w-[80px]">Version</th>
163 {/* Send ProductData */}
164 <tr className="border-b border-border">
165 <td className="p-4 font-medium text-text">Send ProductData</td>
166 {[-1, 1, 2, 3, 4, 5, 6, 7, 8].map(p => (
167 <td key={p} className="p-4 text-center">
168 <div className="flex justify-center">
170 checked={productData[p] || false}
171 onChange={(checked) => {
172 setProductData({ ...productData, [p]: checked });
173 saveSetting('EFTProductDataAuto', p, checked);
179 <td className="p-4 text-center text-xs text-muted">Forever</td>
181 <tr className="border-b border-border">
182 <td colSpan={11} className="p-4 text-sm text-muted bg-surface-2">
183 Should the POS send ProductData information to the Pinpad. This is <u>only enabled in rare circumstances</u> where
184 transactions are being acquired and is not available on all Eft providers.
188 {/* Verify low value payments */}
189 <tr className="border-b border-border bg-surface">
190 <td className="p-4 font-medium text-text">Verify Low Value Payments</td>
191 {[-1, 1, 2, 3, 4, 5, 6, 7, 8].map(p => (
192 <td key={p} className="p-4 text-center">
193 <div className="flex justify-center">
195 checked={lowValueWarning[p] || false}
196 onChange={(checked) => {
197 setLowValueWarning({ ...lowValueWarning, [p]: checked });
198 saveSetting('EftLowValueWarning', p, checked);
204 <td className="p-4 text-center text-xs text-muted">P2255</td>
206 <tr className="border-b border-border">
207 <td colSpan={11} className="p-4 text-sm text-muted bg-surface-2">
208 Allow the POS to prompt for verification of low value eftpos payments before sending them to the pinpad.
209 This is intended to catch simple mistakes such as charging 5 cents on eftpos.
213 {/* Cash Out Warning Level */}
214 <tr className="border-b border-gray-100 bg-surface">
215 <td className="p-4 font-medium text-text">Cash Out Warning Level</td>
216 {[-1, 1, 2, 3, 4, 5, 6, 7, 8].map(p => (
217 <td key={p} className="p-4 text-center">
220 value={cashWarning[p] || ''}
221 onChange={(e) => setCashWarning({ ...cashWarning, [p]: e.target.value })}
222 onBlur={(e) => saveSetting('EftCashWarning', p, e.target.value)}
224 className="w-20 px-2 py-1 border border-border rounded text-center text-sm focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
228 <td className="p-4 text-center text-xs text-muted">P1902</td>
230 <tr className="border-b border-border">
231 <td colSpan={11} className="p-4 text-sm text-muted bg-surface-2">
232 If the cash out amount is above this amount, the POS will prompt the teller to double confirm the amount is correct.
236 {/* Cash Out Extra Copies */}
237 <tr className="border-b border-gray-100 bg-surface">
238 <td className="p-4 font-medium text-text">Cash Out Extra Copies</td>
239 {[-1, 1, 2, 3, 4, 5, 6, 7, 8].map(p => (
240 <td key={p} className="p-4 text-center">
243 value={cashOutCopies[p] || ''}
244 onChange={(e) => setCashOutCopies({ ...cashOutCopies, [p]: e.target.value })}
245 onBlur={(e) => saveSetting('CashOutCopies', p, e.target.value)}
247 className="w-20 px-2 py-1 border border-border rounded text-center text-sm focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
251 <td className="p-4 text-center text-xs text-muted">Forever</td>
253 <tr className="border-b border-border">
254 <td colSpan={11} className="p-4 text-sm text-muted bg-surface-2">
255 How many <em>additional</em> copies of the Eft acquirers receipt should be printed.
259 {/* Print Extended CashOut Slip */}
260 <tr className="border-b border-gray-100 bg-surface">
261 <td className="p-4 font-medium text-text">Print Extended CashOut Slip</td>
262 {[-1, 1, 2, 3, 4, 5, 6, 7, 8].map(p => (
263 <td key={p} className="p-4 text-center">
264 <div className="flex justify-center">
266 checked={extendedSlip[p] || false}
267 onChange={(checked) => {
268 setExtendedSlip({ ...extendedSlip, [p]: checked });
269 saveSetting('CashOutExtendedSlip', p, checked);
275 <td className="p-4 text-center text-xs text-muted">Forever</td>
277 <tr className="border-b border-border">
278 <td colSpan={11} className="p-4 text-sm text-muted bg-surface-2">
279 Print an extended and highlighted POS generated Cash Out slip for the customer to sign acknowledging receipt of the cash.
283 {/* Check Vendor Versions */}
284 <tr className="border-b border-gray-100 bg-surface">
285 <td className="p-4 font-medium text-text">Check Vendor Versions</td>
286 {[-1, 1, 2, 3, 4, 5, 6, 7, 8].map(p => (
287 <td key={p} className="p-4 text-center">
288 <div className="flex justify-center">
290 checked={versionWarning[p] || false}
291 onChange={(checked) => {
292 setVersionWarning({ ...versionWarning, [p]: checked });
293 saveSetting('EftposVersionWarning', p, checked);
299 <td className="p-4 text-center text-xs text-muted">Forever</td>
301 <tr className="border-b border-border">
302 <td colSpan={11} className="p-4 text-sm text-muted bg-surface-2">
303 The POS should check the Eftpos provider's code versions for minimum levels supported by Fieldpine and warn teller if
304 below the minimum. This is intended to capture computers that may have failed to receive required updates. The message is
305 only a warning and the POS will continue to use older versions. Fieldpine support will only provide cursory support for
306 eftpos related issues if unsupported Eft code is used.
310 {/* Permit EFT Refunds */}
311 <tr className="border-b border-gray-100 bg-surface">
312 <td className="p-4 font-medium text-text">Permit EFT Refunds</td>
313 {[-1, 1, 2, 3, 4, 5, 6, 7, 8].map(p => (
314 <td key={p} className="p-4 text-center">
315 <div className="flex justify-center">
317 checked={eftRefund[p] || false}
318 onChange={(checked) => {
319 setEftRefund({ ...eftRefund, [p]: checked });
320 saveSetting('EFTEnRefund', p, checked);
326 <td className="p-4 text-center text-xs text-muted">Forever</td>
328 <tr className="border-b border-border">
329 <td colSpan={11} className="p-4 text-sm text-muted bg-surface-2">
330 Can the POS attempt refund transactions to the pinpad? Refund support often requires additional merchant handling such as
331 merchant refund cards from the acquirer and is not possible on all terminals.
335 {/* Print Eftpos Receipts */}
336 <tr className="border-b border-gray-100 bg-surface">
337 <td className="p-4 font-medium text-text">Print Eftpos Receipts</td>
338 {[-1, 1, 2, 3, 4, 5, 6, 7, 8].map(p => (
339 <td key={p} className="p-4 text-center">
341 value={receiptInstant[p] || 'always'}
343 setReceiptInstant({ ...receiptInstant, [p]: e.target.value });
344 saveSetting('EftposReceiptInstant', p, e.target.value);
346 className="px-2 py-1 border border-border rounded text-sm focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
348 <option value="always">Always</option>
349 <option value="prompt">Prompt</option>
350 <option value="no">None</option>
354 <td className="p-4 text-center text-xs text-muted">Forever</td>
356 <tr className="border-b border-border">
357 <td colSpan={11} className="p-4 text-sm text-muted bg-surface-2">
358 Should Eftpos receipts be printed? By default eftpos pinpads print a customer receipt for all transactions however most
359 customers do not want these slips, so they are simply discarded.
360 <p className="mt-2">Only some Eftpos providers are able to support this option, such as PC-Eftpos from Eftpos New Zealand.</p>
364 {/* Print Eftpos Receipts Inline */}
365 <tr className="border-b border-gray-100 bg-surface">
366 <td className="p-4 font-medium text-text">Print Eftpos Receipts Inline</td>
367 {[-1, 1, 2, 3, 4, 5, 6, 7, 8].map(p => (
368 <td key={p} className="p-4 text-center">
370 value={receiptInline[p] || '0'}
372 setReceiptInline({ ...receiptInline, [p]: e.target.value });
373 saveSetting('PrintEftposInline', p, e.target.value);
375 className="px-2 py-1 border border-border rounded text-sm focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
377 <option value="0">POS chooses</option>
378 <option value="1">Yes</option>
379 <option value="2">No</option>
383 <td className="p-4 text-center text-xs text-muted">P2153</td>
385 <tr className="border-b border-border">
386 <td colSpan={11} className="p-4 text-sm text-muted bg-surface-2">
387 When the POS is printing the initial receipt, should the Eftpos receipt slips be included as well? This is typically set No,
388 if the eftpos provider has already printed the receipt. When "POS chooses" it attempts to decide based on which eftpos
389 provider is in use, but it can get it wrong depending on how the external eftpos system is configured, so you may need to
390 manually select yes/no.
397 {/* Eftpos Provider Settings */}
398 <div className="mt-8 bg-surface rounded-lg shadow p-6">
399 <h2 className="text-xl font-bold text-text mb-6 pb-3 border-b border-border">
400 Eftpos Provider Settings
403 <div className="mb-6">
404 <h3 className="text-lg font-semibold text-[#00946b] mb-4">
405 <a href="https://www.linkly.com.au" target="_blank" rel="noopener noreferrer" className="hover:underline">
409 <div className="space-y-4 max-w-md">
411 <label className="block text-sm font-medium text-text mb-2">Username</label>
414 value={linklyUsername}
415 onChange={(e) => setLinklyUsername(e.target.value)}
416 onBlur={(e) => saveSetting('EftProvider.Linkly.Username', -1, e.target.value)}
417 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
421 <label className="block text-sm font-medium text-text mb-2">Password</label>
424 value={linklyPassword}
425 onChange={(e) => setLinklyPassword(e.target.value)}
426 onBlur={(e) => saveSetting('EftProvider.Linkly.Password', -1, e.target.value)}
427 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
435 <div className="mt-6 bg-info/10 border border-info/30 rounded-lg p-4">
436 <h3 className="text-sm font-semibold text-info mb-2">About EFTPOS Settings</h3>
437 <ul className="text-sm text-info space-y-1">
438 <li>• Settings are applied per profile, allowing different configurations for different lane groups</li>
439 <li>• Changes take effect immediately but may require POS restart on some lanes</li>
440 <li>• Provider-specific features may not be available on all EFTPOS terminals</li>
441 <li>• Consult with your EFTPOS provider before enabling advanced features like ProductData or Refunds</li>