@apptimate/ui 4.7.0 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import React, { useState } from 'react';
|
|
4
|
-
import { Briefcase, CreditCard, Settings, Menu, X, LogOut, User as UserIcon, Building2, ChevronRight, Check } from 'lucide-react';
|
|
4
|
+
import { Briefcase, CreditCard, Settings, Menu, X, LogOut, User as UserIcon, Building2, ChevronRight, Check, PanelLeftClose, PanelLeftOpen } from 'lucide-react';
|
|
5
5
|
import { Dropdown, DropdownItem } from '../base-components/Dropdown';
|
|
6
6
|
|
|
7
7
|
import Link from 'next/link';
|
|
@@ -67,6 +67,7 @@ export function DashboardLayout({
|
|
|
67
67
|
}) {
|
|
68
68
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
|
69
69
|
const [isOrgModalOpen, setIsOrgModalOpen] = useState(false);
|
|
70
|
+
const [isSubSidebarOpen, setIsSubSidebarOpen] = useState(true);
|
|
70
71
|
const pathname = usePathname() || "";
|
|
71
72
|
const router = useRouter();
|
|
72
73
|
|
|
@@ -140,11 +141,22 @@ export function DashboardLayout({
|
|
|
140
141
|
return (
|
|
141
142
|
<div className="flex h-screen bg-[#F4F5F7] overflow-hidden font-sans print:h-auto print:overflow-visible print:bg-white">
|
|
142
143
|
{/* Desktop Global Sidebar */}
|
|
143
|
-
<aside className="hidden md:flex print:hidden w-[80px] bg-white border-r border-gray-200 flex-col items-center py-6 shrink-0 z-
|
|
144
|
+
<aside className="hidden md:flex print:hidden w-[80px] bg-white border-r border-gray-200 flex-col items-center py-6 shrink-0 z-30 relative group">
|
|
144
145
|
<div className="w-12 h-12 flex items-center justify-center mb-8">
|
|
145
146
|
{logo}
|
|
146
147
|
</div>
|
|
147
148
|
|
|
149
|
+
{/* Floating Open Button when Sub-sidebar is closed */}
|
|
150
|
+
{!isSubSidebarOpen && activeMenu && activeMenu.groups.length > 0 && (
|
|
151
|
+
<button
|
|
152
|
+
onClick={() => setIsSubSidebarOpen(true)}
|
|
153
|
+
className="hidden md:flex absolute -right-4 top-6 z-40 w-8 h-8 bg-white border border-gray-200 rounded-full shadow-sm items-center justify-center text-gray-500 hover:text-[#2D3142] hover:bg-gray-50 transition-all outline-none opacity-0 group-hover:opacity-100 cursor-pointer"
|
|
154
|
+
title="Expand Sidebar"
|
|
155
|
+
>
|
|
156
|
+
<PanelLeftOpen size={16} />
|
|
157
|
+
</button>
|
|
158
|
+
)}
|
|
159
|
+
|
|
148
160
|
<style dangerouslySetInnerHTML={{
|
|
149
161
|
__html: `
|
|
150
162
|
.sidebar-scroll {
|
|
@@ -179,7 +191,10 @@ export function DashboardLayout({
|
|
|
179
191
|
return (
|
|
180
192
|
<div
|
|
181
193
|
key={menu.id}
|
|
182
|
-
onClick={() =>
|
|
194
|
+
onClick={() => {
|
|
195
|
+
setActiveMenuId(menu.id);
|
|
196
|
+
setIsSubSidebarOpen(true);
|
|
197
|
+
}}
|
|
183
198
|
className={`flex flex-col items-center justify-center gap-1.5 cursor-pointer transition-colors w-full px-1 ${isActive ? "text-[#2D3142]" : "text-gray-400 hover:text-[#2D3142]"
|
|
184
199
|
}`}
|
|
185
200
|
>
|
|
@@ -226,8 +241,17 @@ export function DashboardLayout({
|
|
|
226
241
|
|
|
227
242
|
{/* Desktop Secondary Sidebar */}
|
|
228
243
|
{activeMenu && activeMenu.groups.length > 0 && (
|
|
229
|
-
|
|
230
|
-
<
|
|
244
|
+
<>
|
|
245
|
+
<aside className={cn(
|
|
246
|
+
"hidden md:flex print:hidden bg-white border-gray-200 flex-col py-8 shrink-0 z-10 transition-all duration-300 overflow-hidden group",
|
|
247
|
+
isSubSidebarOpen ? "w-[210px]" : "w-0 opacity-0 px-0"
|
|
248
|
+
)}>
|
|
249
|
+
<div className="flex items-center justify-between px-6 mb-6 w-[210px]">
|
|
250
|
+
<h2 className="text-xl font-bold text-[#2D3142]/80 truncate">{activeMenu.label}</h2>
|
|
251
|
+
<button onClick={() => setIsSubSidebarOpen(false)} className="text-gray-400 hover:text-[#2D3142] transition-colors flex-shrink-0 outline-none opacity-0 group-hover:opacity-100 cursor-pointer">
|
|
252
|
+
<PanelLeftClose size={18} />
|
|
253
|
+
</button>
|
|
254
|
+
</div>
|
|
231
255
|
<div className="flex-1 overflow-y-auto px-4 space-y-6">
|
|
232
256
|
{activeMenu.groups.map((group) => (
|
|
233
257
|
<div key={group.id}>
|
|
@@ -296,6 +320,8 @@ export function DashboardLayout({
|
|
|
296
320
|
</div>
|
|
297
321
|
)}
|
|
298
322
|
</aside>
|
|
323
|
+
|
|
324
|
+
</>
|
|
299
325
|
)}
|
|
300
326
|
|
|
301
327
|
{/* Main Content Area & Mobile Header */}
|
|
@@ -57,24 +57,24 @@ export function ProductTransactionScreen({
|
|
|
57
57
|
setLineItems(prev => prev.map(l => {
|
|
58
58
|
if (l.is_free_qty || !l.variant_id || !evaluatedRules[l.variant_id]) return l;
|
|
59
59
|
const rules = evaluatedRules[l.variant_id];
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
const variantPriceField = config.priceField === 'default_cost_price' ? 'cost_price' : 'sale_price';
|
|
62
62
|
const actualVariant = l.item.variants?.find((v: any) => v.id === l.variant_id);
|
|
63
63
|
const batchPriceField = config.priceField === 'default_cost_price' ? 'cost_price' : 'selling_price';
|
|
64
64
|
const batchPrice = ['sales', 'quotation'].includes(config.type) && l.batches?.[0] ? (l.batches[0] as any)[batchPriceField] : undefined;
|
|
65
65
|
const basePrice = (batchPrice !== undefined && batchPrice !== null)
|
|
66
66
|
? Number(batchPrice)
|
|
67
|
-
: (actualVariant
|
|
68
|
-
? Number(actualVariant[variantPriceField] || l.item[config.priceField] || 0)
|
|
67
|
+
: (actualVariant
|
|
68
|
+
? Number(actualVariant[variantPriceField] || l.item[config.priceField] || 0)
|
|
69
69
|
: Number(l.item[config.priceField] || 0));
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
const uoms = l.item.uom?.uom_group?.uoms || [];
|
|
72
72
|
const currentUom = uoms.find((u: any) => u.id === (l.uom_id || l.item.uom_id));
|
|
73
73
|
const factor = Number(currentUom?.conversion_factor) || 1;
|
|
74
74
|
const effectiveQty = l.quantity * factor;
|
|
75
75
|
|
|
76
76
|
const priceDetails = PriceCalculationService.calculatePriceDetails(basePrice, effectiveQty, rules, l.batches?.[0]?.batch_id);
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
return {
|
|
79
79
|
...l,
|
|
80
80
|
pricing_rules: rules,
|
|
@@ -111,6 +111,13 @@ export function ProductTransactionScreen({
|
|
|
111
111
|
setWarehouseId(initialData.warehouse_id);
|
|
112
112
|
if (initialData.warehouse?.name) setWarehouseName(initialData.warehouse.name);
|
|
113
113
|
}
|
|
114
|
+
if (initialData.transaction_date || initialData.order_date || initialData.quotation_date || initialData.date) {
|
|
115
|
+
setTransactionDate(initialData.transaction_date || initialData.order_date || initialData.quotation_date || initialData.date);
|
|
116
|
+
}
|
|
117
|
+
if (initialData.expected_delivery_date || initialData.expected_delivery) {
|
|
118
|
+
setExpectedDeliveryDate(initialData.expected_delivery_date || initialData.expected_delivery);
|
|
119
|
+
}
|
|
120
|
+
|
|
114
121
|
if (initialData.lines && Array.isArray(initialData.lines)) {
|
|
115
122
|
setLineItems(
|
|
116
123
|
initialData.lines.map((l: any) => {
|
|
@@ -135,7 +142,7 @@ export function ProductTransactionScreen({
|
|
|
135
142
|
};
|
|
136
143
|
})
|
|
137
144
|
);
|
|
138
|
-
|
|
145
|
+
|
|
139
146
|
// Populate pricing rules for existing items if party is selected
|
|
140
147
|
if (initialData.party_id && ['sales', 'pos', 'quotation'].includes(config.type)) {
|
|
141
148
|
evaluatePriceLists({
|
|
@@ -152,7 +159,7 @@ export function ProductTransactionScreen({
|
|
|
152
159
|
return line;
|
|
153
160
|
}));
|
|
154
161
|
}
|
|
155
|
-
}).catch(() => {});
|
|
162
|
+
}).catch(() => { });
|
|
156
163
|
}
|
|
157
164
|
}
|
|
158
165
|
}
|
|
@@ -186,7 +193,7 @@ export function ProductTransactionScreen({
|
|
|
186
193
|
? Number(batchPrice)
|
|
187
194
|
: (actualVariant ? Number(actualVariant[variantPriceField] || itemWithVariants[config.priceField] || 0) : Number(itemWithVariants[config.priceField] || 0));
|
|
188
195
|
const qty = extra?.quantity || 1;
|
|
189
|
-
|
|
196
|
+
|
|
190
197
|
const uomId = itemWithVariants.uom_id;
|
|
191
198
|
const uoms = itemWithVariants.uom?.uom_group?.uoms || [];
|
|
192
199
|
const currentUom = uoms.find((u: any) => u.id === uomId);
|
|
@@ -206,7 +213,7 @@ export function ProductTransactionScreen({
|
|
|
206
213
|
discountAmt = priceDetails.discountAmount * factor;
|
|
207
214
|
appliedRule = priceDetails.appliedRule;
|
|
208
215
|
}
|
|
209
|
-
|
|
216
|
+
|
|
210
217
|
const newLine: TransactionLineItem = {
|
|
211
218
|
uid: `${itemWithVariants.id}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
|
212
219
|
item: itemWithVariants,
|
|
@@ -253,14 +260,14 @@ export function ProductTransactionScreen({
|
|
|
253
260
|
const batchPrice = ['sales', 'quotation'].includes(config.type) && updatedLine.batches?.[0] ? (updatedLine.batches[0] as any)[batchPriceField] : undefined;
|
|
254
261
|
const basePrice = (batchPrice !== undefined && batchPrice !== null)
|
|
255
262
|
? Number(batchPrice)
|
|
256
|
-
: (actualVariant
|
|
257
|
-
? Number(actualVariant[variantPriceField] || updatedLine.item[config.priceField] || 0)
|
|
263
|
+
: (actualVariant
|
|
264
|
+
? Number(actualVariant[variantPriceField] || updatedLine.item[config.priceField] || 0)
|
|
258
265
|
: Number(updatedLine.item[config.priceField] || 0));
|
|
259
|
-
|
|
266
|
+
|
|
260
267
|
const uoms = updatedLine.item.uom?.uom_group?.uoms || [];
|
|
261
268
|
const currentUom = uoms.find((u: any) => u.id === (updatedLine.uom_id || updatedLine.item.uom_id));
|
|
262
269
|
const factor = Number(currentUom?.conversion_factor) || 1;
|
|
263
|
-
|
|
270
|
+
|
|
264
271
|
const effectiveQty = updatedLine.quantity * factor;
|
|
265
272
|
|
|
266
273
|
console.log("DEBUG: handleUpdateLine rules -> ", {
|
|
@@ -275,7 +282,7 @@ export function ProductTransactionScreen({
|
|
|
275
282
|
updatedLine.line_total = (updatedLine.unit_price * updatedLine.quantity) - updatedLine.discount_amount;
|
|
276
283
|
} else if (isQtyChanged || isUomChanged || isPriceChanged || isDiscountChanged) {
|
|
277
284
|
updatedLine.line_total = (updatedLine.unit_price * updatedLine.quantity) - updatedLine.discount_amount;
|
|
278
|
-
|
|
285
|
+
|
|
279
286
|
const isManualPriceOverride = isPriceChanged && !isUomChanged;
|
|
280
287
|
if (isManualPriceOverride) {
|
|
281
288
|
updatedLine.applied_price_rule = undefined;
|
|
@@ -286,6 +293,14 @@ export function ProductTransactionScreen({
|
|
|
286
293
|
updatedLine.batches[0].quantity = updatedLine.quantity;
|
|
287
294
|
}
|
|
288
295
|
|
|
296
|
+
if (updatedLine.is_free_qty) {
|
|
297
|
+
updatedLine.unit_price = 0;
|
|
298
|
+
updatedLine.discount_amount = 0;
|
|
299
|
+
updatedLine.discount_percent = 0;
|
|
300
|
+
updatedLine.line_total = 0;
|
|
301
|
+
updatedLine.applied_price_rule = undefined;
|
|
302
|
+
}
|
|
303
|
+
|
|
289
304
|
return updatedLine;
|
|
290
305
|
})
|
|
291
306
|
);
|
|
@@ -470,7 +485,7 @@ export function ProductTransactionScreen({
|
|
|
470
485
|
expectedDeliveryDate={expectedDeliveryDate}
|
|
471
486
|
notes={notes}
|
|
472
487
|
payments={payments}
|
|
473
|
-
onPartyChange={async (id, party) => {
|
|
488
|
+
onPartyChange={async (id, party) => {
|
|
474
489
|
if (partyId !== id && lineItems.length > 0 && ['sales', 'pos', 'quotation'].includes(config.type)) {
|
|
475
490
|
if (hasActivePriceLists) {
|
|
476
491
|
toast.loading('Checking price changes...', { id: 'price-calc' });
|
|
@@ -480,12 +495,12 @@ export function ProductTransactionScreen({
|
|
|
480
495
|
transaction_type: config.type,
|
|
481
496
|
items: lineItems.filter(l => l.variant_id && !l.is_free_qty).map(l => ({ variant_id: l.variant_id! }))
|
|
482
497
|
});
|
|
483
|
-
|
|
498
|
+
|
|
484
499
|
toast.dismiss('price-calc');
|
|
485
|
-
|
|
500
|
+
|
|
486
501
|
if (res.is_success && res.result) {
|
|
487
502
|
const evaluatedRules = res.result as Record<number, any[]>;
|
|
488
|
-
|
|
503
|
+
|
|
489
504
|
let hasActualPriceChanges = false;
|
|
490
505
|
for (const l of lineItems) {
|
|
491
506
|
if (!l.is_free_qty && l.variant_id && evaluatedRules[l.variant_id]) {
|
|
@@ -496,24 +511,24 @@ export function ProductTransactionScreen({
|
|
|
496
511
|
const batchPrice = ['sales', 'quotation'].includes(config.type) && l.batches?.[0] ? (l.batches[0] as any)[batchPriceField] : undefined;
|
|
497
512
|
const basePrice = (batchPrice !== undefined && batchPrice !== null)
|
|
498
513
|
? Number(batchPrice)
|
|
499
|
-
: (actualVariant
|
|
500
|
-
? Number(actualVariant[variantPriceField] || l.item[config.priceField] || 0)
|
|
514
|
+
: (actualVariant
|
|
515
|
+
? Number(actualVariant[variantPriceField] || l.item[config.priceField] || 0)
|
|
501
516
|
: Number(l.item[config.priceField] || 0));
|
|
502
|
-
|
|
517
|
+
|
|
503
518
|
const uoms = l.item.uom?.uom_group?.uoms || [];
|
|
504
519
|
const currentUom = uoms.find((u: any) => u.id === (l.uom_id || l.item.uom_id));
|
|
505
520
|
const factor = Number(currentUom?.conversion_factor) || 1;
|
|
506
521
|
const effectiveQty = l.quantity * factor;
|
|
507
522
|
|
|
508
523
|
const priceDetails = PriceCalculationService.calculatePriceDetails(basePrice, effectiveQty, rules, l.batches?.[0]?.batch_id);
|
|
509
|
-
|
|
524
|
+
|
|
510
525
|
if (Math.abs((priceDetails.unitPrice * factor) - l.unit_price) > 0.001) {
|
|
511
526
|
hasActualPriceChanges = true;
|
|
512
527
|
break;
|
|
513
528
|
}
|
|
514
529
|
}
|
|
515
530
|
}
|
|
516
|
-
|
|
531
|
+
|
|
517
532
|
if (hasActualPriceChanges) {
|
|
518
533
|
const previousRulesApplied = lineItems.some(l => l.applied_price_rule);
|
|
519
534
|
setPriceEvaluationModal({
|
|
@@ -542,8 +557,8 @@ export function ProductTransactionScreen({
|
|
|
542
557
|
}
|
|
543
558
|
}
|
|
544
559
|
}
|
|
545
|
-
setPartyId(id);
|
|
546
|
-
setPartyName(party?.name ?? null);
|
|
560
|
+
setPartyId(id);
|
|
561
|
+
setPartyName(party?.name ?? null);
|
|
547
562
|
}}
|
|
548
563
|
onWarehouseChange={(id, name) => { setWarehouseId(id); setWarehouseName(name ?? null); }}
|
|
549
564
|
onDateChange={setTransactionDate}
|
|
@@ -574,15 +589,15 @@ export function ProductTransactionScreen({
|
|
|
574
589
|
size="sm"
|
|
575
590
|
>
|
|
576
591
|
<div className="text-[14px] text-foreground-1 leading-relaxed">
|
|
577
|
-
{priceEvaluationModal.type === 'confirm'
|
|
592
|
+
{priceEvaluationModal.type === 'confirm'
|
|
578
593
|
? "A few items have price changes based on the price list. Do you want to apply them?"
|
|
579
594
|
: "Prices need to be changed based on the party selection."}
|
|
580
595
|
</div>
|
|
581
596
|
<ModalFooter>
|
|
582
597
|
{priceEvaluationModal.type === 'confirm' && (
|
|
583
|
-
<Button
|
|
584
|
-
variant="flat"
|
|
585
|
-
color="secondary"
|
|
598
|
+
<Button
|
|
599
|
+
variant="flat"
|
|
600
|
+
color="secondary"
|
|
586
601
|
onClick={() => {
|
|
587
602
|
setPartyId(priceEvaluationModal.pendingPartyId);
|
|
588
603
|
setPartyName(priceEvaluationModal.pendingParty?.name ?? null);
|
|
@@ -592,8 +607,8 @@ export function ProductTransactionScreen({
|
|
|
592
607
|
No
|
|
593
608
|
</Button>
|
|
594
609
|
)}
|
|
595
|
-
<Button
|
|
596
|
-
color="primary"
|
|
610
|
+
<Button
|
|
611
|
+
color="primary"
|
|
597
612
|
onClick={() => {
|
|
598
613
|
applyEvaluatedPrices(priceEvaluationModal.evaluatedRules);
|
|
599
614
|
setPartyId(priceEvaluationModal.pendingPartyId);
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
import { Button, Input, ModalFooter } from "../index";
|
|
5
|
+
import { PaymentSection, PaymentEntry } from "../common-components/PaymentSection";
|
|
6
|
+
import toast from "react-hot-toast";
|
|
7
|
+
|
|
8
|
+
interface PaymentFormProps {
|
|
9
|
+
invoice: any;
|
|
10
|
+
onSuccess: () => void;
|
|
11
|
+
onCancel: () => void;
|
|
12
|
+
onRecordPayment: (payload: any) => Promise<{ is_success: boolean; message?: string }>;
|
|
13
|
+
fetchPaymentModes: () => Promise<any>;
|
|
14
|
+
fetchBankAccounts: () => Promise<any>;
|
|
15
|
+
fetchChequeLeaves?: (query: string, page?: number) => Promise<any>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function PaymentForm({
|
|
19
|
+
invoice,
|
|
20
|
+
onSuccess,
|
|
21
|
+
onCancel,
|
|
22
|
+
onRecordPayment,
|
|
23
|
+
fetchPaymentModes,
|
|
24
|
+
fetchBankAccounts,
|
|
25
|
+
fetchChequeLeaves,
|
|
26
|
+
}: PaymentFormProps) {
|
|
27
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
28
|
+
const outstanding = Math.abs(Number(invoice.amount_due || 0) - Number(invoice.amount_settled || invoice.amount_received || 0));
|
|
29
|
+
|
|
30
|
+
const [paymentDate, setPaymentDate] = useState(new Date().toISOString().split("T")[0]);
|
|
31
|
+
const [reference, setReference] = useState("");
|
|
32
|
+
const [remarks, setRemarks] = useState("");
|
|
33
|
+
const [payments, setPayments] = useState<PaymentEntry[]>([]);
|
|
34
|
+
|
|
35
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
36
|
+
e.preventDefault();
|
|
37
|
+
if (payments.length === 0) {
|
|
38
|
+
toast.error("Please add at least one payment");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const totalToPay = payments.reduce((sum, p) => sum + (Number(p.amount) || 0), 0);
|
|
43
|
+
if (totalToPay > outstanding + 0.01) {
|
|
44
|
+
toast.error(`Payment amount exceeds outstanding balance (${outstanding.toFixed(2)})`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setIsSubmitting(true);
|
|
49
|
+
let successCount = 0;
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
for (const p of payments) {
|
|
53
|
+
if (Number(p.amount) <= 0) continue;
|
|
54
|
+
const payload: any = {
|
|
55
|
+
invoice_id: invoice.id,
|
|
56
|
+
payment_date: paymentDate,
|
|
57
|
+
payment_amount: p.amount,
|
|
58
|
+
payment_method: p.mode_code,
|
|
59
|
+
payment_mode_id: p.mode_id,
|
|
60
|
+
remarks: remarks || null,
|
|
61
|
+
reference: reference || null,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
if (p.metadata) {
|
|
65
|
+
if (p.metadata.reference) payload.reference = p.metadata.reference;
|
|
66
|
+
if (p.metadata.bank_account_id) payload.bank_account_id = p.metadata.bank_account_id;
|
|
67
|
+
payload.mode_metadata = p.metadata;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const res = await onRecordPayment(payload);
|
|
71
|
+
if (res.is_success) successCount++;
|
|
72
|
+
else toast.error(`Failed for ${p.mode_name}: ${res.message}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (successCount > 0) {
|
|
76
|
+
toast.success("Payment recorded successfully");
|
|
77
|
+
onSuccess();
|
|
78
|
+
}
|
|
79
|
+
} catch (e: any) {
|
|
80
|
+
toast.error(e.message || "Error recording payment");
|
|
81
|
+
} finally {
|
|
82
|
+
setIsSubmitting(false);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
88
|
+
<div className="flex flex-col gap-1.5">
|
|
89
|
+
<label className="text-[11px] font-bold text-gray-500 uppercase tracking-wider">Payment Date *</label>
|
|
90
|
+
<Input
|
|
91
|
+
type="date"
|
|
92
|
+
value={paymentDate}
|
|
93
|
+
onChange={(e) => setPaymentDate(e.target.value)}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<PaymentSection
|
|
98
|
+
totalAmount={outstanding}
|
|
99
|
+
payments={payments}
|
|
100
|
+
onPaymentsChange={setPayments}
|
|
101
|
+
fetchPaymentModes={async () => {
|
|
102
|
+
const res = await fetchPaymentModes();
|
|
103
|
+
if (res.is_success && Array.isArray(res.result)) {
|
|
104
|
+
res.result = res.result.filter((m: any) => {
|
|
105
|
+
const code = m.code?.toLowerCase();
|
|
106
|
+
return code !== "credit" && code !== "credit-metal";
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return res;
|
|
110
|
+
}}
|
|
111
|
+
fetchBankAccounts={fetchBankAccounts}
|
|
112
|
+
fetchChequeLeaves={fetchChequeLeaves}
|
|
113
|
+
/>
|
|
114
|
+
|
|
115
|
+
<div className="flex flex-col gap-4 mt-2">
|
|
116
|
+
<div className="flex flex-col gap-1.5">
|
|
117
|
+
<label className="text-[11px] font-bold text-gray-500 uppercase tracking-wider">Reference No</label>
|
|
118
|
+
<Input
|
|
119
|
+
value={reference}
|
|
120
|
+
onChange={(e) => setReference(e.target.value)}
|
|
121
|
+
placeholder="Leave blank to auto-generate"
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
124
|
+
<div className="flex flex-col gap-1.5">
|
|
125
|
+
<label className="text-[11px] font-bold text-gray-500 uppercase tracking-wider">Remarks</label>
|
|
126
|
+
<textarea
|
|
127
|
+
value={remarks}
|
|
128
|
+
onChange={(e) => setRemarks(e.target.value)}
|
|
129
|
+
rows={2}
|
|
130
|
+
placeholder="Optional notes..."
|
|
131
|
+
className="w-full bg-white border-[1.5px] border-gray-200 rounded-[10px] px-3.5 py-2.5 text-[13px] text-gray-800 outline-none transition-all hover:border-gray-300 focus:border-primary/50 resize-none"
|
|
132
|
+
/>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<ModalFooter>
|
|
137
|
+
<Button type="button" variant="flat" color="default" onClick={onCancel}>Cancel</Button>
|
|
138
|
+
<Button type="submit" color="primary" isLoading={isSubmitting}>Record Payment</Button>
|
|
139
|
+
</ModalFooter>
|
|
140
|
+
</form>
|
|
141
|
+
);
|
|
142
|
+
}
|
package/src/index.tsx
CHANGED