@apptimate/ui 3.1.0 → 3.3.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,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apptimate/ui",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"main": "src/index.tsx",
|
|
5
5
|
"types": "src/index.tsx",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@apptimate/core-lib": "*",
|
|
8
|
+
"apexcharts": "^5.15.2",
|
|
8
9
|
"class-variance-authority": "^0.7.1",
|
|
9
10
|
"clsx": "^2.1.1",
|
|
10
11
|
"framer-motion": "^12.38.0",
|
|
11
12
|
"lucide-react": "^1.14.0",
|
|
12
13
|
"react": "^19.2.5",
|
|
14
|
+
"react-apexcharts": "^2.1.1",
|
|
13
15
|
"recharts": "^2.15.4",
|
|
14
|
-
"tailwind-merge": "^3.5.0"
|
|
15
|
-
"apexcharts": "^5.11.0",
|
|
16
|
-
"react-apexcharts": "^2.1.0"
|
|
16
|
+
"tailwind-merge": "^3.5.0"
|
|
17
17
|
},
|
|
18
18
|
"publishConfig": {
|
|
19
19
|
"access": "public"
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import toast from "react-hot-toast";
|
|
3
|
-
import { Button } from "./Button";
|
|
4
|
-
|
|
5
|
-
export const handleFinanceError = (error: any, defaultMessage: string = "An error occurred") => {
|
|
6
|
-
const errMsg = typeof error === "string" ? error : (error?.message || defaultMessage);
|
|
7
|
-
|
|
8
|
-
if (errMsg.toLowerCase().includes("fiscal year")) {
|
|
9
|
-
toast.error((t) => (
|
|
10
|
-
<div className="flex flex-col gap-2 mt-1">
|
|
11
|
-
<span>{errMsg}</span>
|
|
12
|
-
<Button size="small" variant="solid" color="primary" className="w-fit mt-1" onClick={() => {
|
|
13
|
-
toast.dismiss(t.id);
|
|
14
|
-
window.open("/finance/fiscal-years", "_blank");
|
|
15
|
-
}}>
|
|
16
|
-
Open Fiscal Years
|
|
17
|
-
</Button>
|
|
18
|
-
</div>
|
|
19
|
-
), { duration: 8000 });
|
|
20
|
-
} else {
|
|
21
|
-
toast.error(errMsg);
|
|
22
|
-
}
|
|
23
|
-
};
|
|
1
|
+
import React from "react";
|
|
2
|
+
import toast from "react-hot-toast";
|
|
3
|
+
import { Button } from "./Button";
|
|
4
|
+
|
|
5
|
+
export const handleFinanceError = (error: any, defaultMessage: string = "An error occurred") => {
|
|
6
|
+
const errMsg = typeof error === "string" ? error : (error?.message || defaultMessage);
|
|
7
|
+
|
|
8
|
+
if (errMsg.toLowerCase().includes("fiscal year")) {
|
|
9
|
+
toast.error((t) => (
|
|
10
|
+
<div className="flex flex-col gap-2 mt-1">
|
|
11
|
+
<span>{errMsg}</span>
|
|
12
|
+
<Button size="small" variant="solid" color="primary" className="w-fit mt-1" onClick={() => {
|
|
13
|
+
toast.dismiss(t.id);
|
|
14
|
+
window.open("/finance/fiscal-years", "_blank");
|
|
15
|
+
}}>
|
|
16
|
+
Open Fiscal Years
|
|
17
|
+
</Button>
|
|
18
|
+
</div>
|
|
19
|
+
), { duration: 8000 });
|
|
20
|
+
} else {
|
|
21
|
+
toast.error(errMsg);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -19,6 +19,8 @@ export type DashboardMenuConfig = {
|
|
|
19
19
|
id: string;
|
|
20
20
|
label: string;
|
|
21
21
|
path: string;
|
|
22
|
+
icon?: React.ReactNode;
|
|
23
|
+
badge?: React.ReactNode;
|
|
22
24
|
}[]
|
|
23
25
|
}[]
|
|
24
26
|
};
|
|
@@ -47,7 +49,8 @@ export function DashboardLayout({
|
|
|
47
49
|
onProfile,
|
|
48
50
|
organizations = [],
|
|
49
51
|
selectedOrganization,
|
|
50
|
-
onOrganizationChange
|
|
52
|
+
onOrganizationChange,
|
|
53
|
+
extraHeaderContent
|
|
51
54
|
}: {
|
|
52
55
|
children: React.ReactNode;
|
|
53
56
|
logo?: React.ReactNode;
|
|
@@ -60,6 +63,7 @@ export function DashboardLayout({
|
|
|
60
63
|
organizations?: OrganizationConfig[];
|
|
61
64
|
selectedOrganization?: OrganizationConfig | null;
|
|
62
65
|
onOrganizationChange?: (org: OrganizationConfig) => void;
|
|
66
|
+
extraHeaderContent?: React.ReactNode;
|
|
63
67
|
}) {
|
|
64
68
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
|
65
69
|
const [isOrgModalOpen, setIsOrgModalOpen] = useState(false);
|
|
@@ -120,14 +124,42 @@ export function DashboardLayout({
|
|
|
120
124
|
{logo}
|
|
121
125
|
</div>
|
|
122
126
|
|
|
123
|
-
<
|
|
127
|
+
<style dangerouslySetInnerHTML={{
|
|
128
|
+
__html: `
|
|
129
|
+
.sidebar-scroll {
|
|
130
|
+
overflow-y: hidden;
|
|
131
|
+
}
|
|
132
|
+
.sidebar-scroll:hover {
|
|
133
|
+
overflow-y: auto;
|
|
134
|
+
}
|
|
135
|
+
/* Custom Scrollbar for Webkit */
|
|
136
|
+
.sidebar-scroll::-webkit-scrollbar {
|
|
137
|
+
width: 4px;
|
|
138
|
+
}
|
|
139
|
+
.sidebar-scroll::-webkit-scrollbar-track {
|
|
140
|
+
background: transparent;
|
|
141
|
+
}
|
|
142
|
+
.sidebar-scroll::-webkit-scrollbar-thumb {
|
|
143
|
+
background: #cbd5e1;
|
|
144
|
+
border-radius: 4px;
|
|
145
|
+
}
|
|
146
|
+
/* For Firefox */
|
|
147
|
+
.sidebar-scroll {
|
|
148
|
+
scrollbar-width: thin;
|
|
149
|
+
scrollbar-color: transparent transparent;
|
|
150
|
+
}
|
|
151
|
+
.sidebar-scroll:hover {
|
|
152
|
+
scrollbar-color: #cbd5e1 transparent;
|
|
153
|
+
}
|
|
154
|
+
`}} />
|
|
155
|
+
<nav className="flex flex-col gap-6 flex-1 w-full sidebar-scroll pb-4">
|
|
124
156
|
{menus.map((menu) => {
|
|
125
157
|
const isActive = activeMenuId === menu.id;
|
|
126
158
|
return (
|
|
127
159
|
<div
|
|
128
160
|
key={menu.id}
|
|
129
161
|
onClick={() => setActiveMenuId(menu.id)}
|
|
130
|
-
className={`flex flex-col items-center justify-center gap-1.5 cursor-pointer transition-colors ${isActive ? "text-[#2D3142]" : "text-gray-400 hover:text-[#2D3142]"
|
|
162
|
+
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]"
|
|
131
163
|
}`}
|
|
132
164
|
>
|
|
133
165
|
{/* Clone the icon to dynamically apply styling based on active state */}
|
|
@@ -135,7 +167,7 @@ export function DashboardLayout({
|
|
|
135
167
|
size: 22,
|
|
136
168
|
className: isActive ? "stroke-[2.5]" : "stroke-[2]"
|
|
137
169
|
})}
|
|
138
|
-
<span className={`text-[11px] text-center leading-tight
|
|
170
|
+
<span className={`text-[11px] text-center leading-tight truncate block w-full max-w-[70px] ${isActive ? "font-bold" : "font-medium"}`}>{menu.label}</span>
|
|
139
171
|
</div>
|
|
140
172
|
);
|
|
141
173
|
})}
|
|
@@ -178,7 +210,9 @@ export function DashboardLayout({
|
|
|
178
210
|
<div className="flex-1 overflow-y-auto px-4 space-y-6">
|
|
179
211
|
{activeMenu.groups.map((group) => (
|
|
180
212
|
<div key={group.id}>
|
|
181
|
-
|
|
213
|
+
{group.label && (
|
|
214
|
+
<h3 className="px-2 text-xs font-bold text-gray-400 uppercase tracking-wider mb-2 empty:hidden">{group.label}</h3>
|
|
215
|
+
)}
|
|
182
216
|
<nav className="flex flex-col gap-1">
|
|
183
217
|
{group.items.map((item) => {
|
|
184
218
|
// Find the longest matching path in this group to avoid parent paths being active
|
|
@@ -191,18 +225,28 @@ export function DashboardLayout({
|
|
|
191
225
|
: !externalPaths.some(ext => item.path.startsWith(ext));
|
|
192
226
|
const href = basePath && isInternal ? item.path.replace(basePath, "") || "/" : item.path;
|
|
193
227
|
|
|
194
|
-
const className = `px-3 py-2 rounded-lg text-sm transition-colors
|
|
228
|
+
const className = `px-3 py-2 rounded-lg text-sm transition-colors flex items-center justify-between ${isItemActive
|
|
195
229
|
? "bg-[#F4F5F7] text-[#2D3142] font-semibold"
|
|
196
230
|
: "text-gray-500 font-medium hover:bg-gray-50"
|
|
197
231
|
}`;
|
|
198
232
|
|
|
233
|
+
const content = (
|
|
234
|
+
<>
|
|
235
|
+
<div className="flex items-center gap-3">
|
|
236
|
+
{item.icon && <span className={`${isItemActive ? 'text-[#2D3142]' : 'text-gray-400'}`}>{item.icon}</span>}
|
|
237
|
+
<span>{item.label}</span>
|
|
238
|
+
</div>
|
|
239
|
+
{item.badge && <div>{item.badge}</div>}
|
|
240
|
+
</>
|
|
241
|
+
);
|
|
242
|
+
|
|
199
243
|
return isInternal ? (
|
|
200
244
|
<Link key={item.id} href={href} className={className}>
|
|
201
|
-
{
|
|
245
|
+
{content}
|
|
202
246
|
</Link>
|
|
203
247
|
) : (
|
|
204
248
|
<a key={item.id} href={href} className={className}>
|
|
205
|
-
{
|
|
249
|
+
{content}
|
|
206
250
|
</a>
|
|
207
251
|
);
|
|
208
252
|
})}
|
|
@@ -469,7 +513,8 @@ export function DashboardLayout({
|
|
|
469
513
|
</div>
|
|
470
514
|
</div>
|
|
471
515
|
|
|
472
|
-
<style dangerouslySetInnerHTML={{
|
|
516
|
+
<style dangerouslySetInnerHTML={{
|
|
517
|
+
__html: `
|
|
473
518
|
@keyframes orgModalIn {
|
|
474
519
|
from {
|
|
475
520
|
opacity: 0;
|
|
@@ -4,6 +4,7 @@ import React, { useState, useEffect, useCallback, useMemo, useRef } from "react"
|
|
|
4
4
|
import { cn } from "@apptimate/core-lib";
|
|
5
5
|
import { CreditCard, Banknote, Building, Trash2, Loader2, Split, Check, AlertCircle, AlertTriangle, CheckCircle } from "lucide-react";
|
|
6
6
|
import { HintIcon } from "../base-components/HintIcon";
|
|
7
|
+
import { AsyncSearchableSelect } from "../base-components/SearchableSelect";
|
|
7
8
|
|
|
8
9
|
/* ── Types (mirroring sales POS types) ── */
|
|
9
10
|
|
|
@@ -63,7 +64,7 @@ export interface PaymentSectionProps {
|
|
|
63
64
|
/** Special mode codes that are allowed (e.g. 'credit-gold') */
|
|
64
65
|
allowedSpecialModes?: string[];
|
|
65
66
|
/** Fetcher for cheque leaves (for auto-complete) */
|
|
66
|
-
fetchChequeLeaves?: (query: string) => Promise<{ is_success: boolean; result?: any[] }>;
|
|
67
|
+
fetchChequeLeaves?: (query: string, page: number) => Promise<{ is_success: boolean; result?: any[] }>;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
/* ── Component ── */
|
|
@@ -328,7 +329,7 @@ interface PaymentEntryRowProps {
|
|
|
328
329
|
onUpdate: (updates: Partial<PaymentEntry>) => void;
|
|
329
330
|
onRemove: () => void;
|
|
330
331
|
showRemove?: boolean;
|
|
331
|
-
fetchChequeLeaves?: (query: string) => Promise<{ is_success: boolean; result?: any[] }>;
|
|
332
|
+
fetchChequeLeaves?: (query: string, page: number) => Promise<{ is_success: boolean; result?: any[] }>;
|
|
332
333
|
}
|
|
333
334
|
|
|
334
335
|
function PaymentEntryRow({ entry, mode, bankAccounts, materialTypes = [], onUpdate, onRemove, showRemove = true, fetchChequeLeaves }: PaymentEntryRowProps) {
|
|
@@ -371,7 +372,12 @@ function PaymentEntryRow({ entry, mode, bankAccounts, materialTypes = [], onUpda
|
|
|
371
372
|
{/* Dynamic sub-fields from field_config */}
|
|
372
373
|
{fields.length > 0 && (
|
|
373
374
|
<div className="grid grid-cols-2 gap-2.5">
|
|
374
|
-
{fields.map((field: PaymentModeField) =>
|
|
375
|
+
{fields.map((field: PaymentModeField) => {
|
|
376
|
+
if (field.key === "bank_account_id" && entry.mode_code === "cheque" && fetchChequeLeaves) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return (
|
|
375
381
|
<div key={field.key} className="flex flex-col gap-1">
|
|
376
382
|
<label className="text-[11px] text-foreground-subtle font-bold uppercase tracking-wider flex items-center">
|
|
377
383
|
{field.label}
|
|
@@ -409,20 +415,53 @@ function PaymentEntryRow({ entry, mode, bankAccounts, materialTypes = [], onUpda
|
|
|
409
415
|
{materialTypes.map((m) => (<option key={m.id} value={m.id}>{m.name}</option>))}
|
|
410
416
|
</select>
|
|
411
417
|
) : field.key === "cheque_number" && fetchChequeLeaves ? (
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
const
|
|
418
|
-
if (
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
418
|
+
<>
|
|
419
|
+
<AsyncSearchableSelect
|
|
420
|
+
defaultValue={entry.metadata[field.key] ? { cheque_number: entry.metadata[field.key] } : undefined}
|
|
421
|
+
onChange={(val, selected: any) => {
|
|
422
|
+
const leafObj = Array.isArray(selected) ? selected[0] : selected;
|
|
423
|
+
const newMetadata = { ...entry.metadata, [field.key]: val, cheque_leaf_id: leafObj?.id || "" };
|
|
424
|
+
if (leafObj?.cheque_book?.bank_account) {
|
|
425
|
+
const ba = leafObj.cheque_book.bank_account;
|
|
426
|
+
newMetadata.bank_account_id = ba.id;
|
|
427
|
+
newMetadata._display_bank_name = ba.bank_name;
|
|
428
|
+
newMetadata._display_branch = ba.branch;
|
|
429
|
+
} else {
|
|
430
|
+
delete newMetadata.bank_account_id;
|
|
431
|
+
delete newMetadata._display_bank_name;
|
|
432
|
+
delete newMetadata._display_branch;
|
|
433
|
+
}
|
|
434
|
+
onUpdate({ metadata: newMetadata });
|
|
435
|
+
}}
|
|
436
|
+
loadOptions={async (search, page) => {
|
|
437
|
+
if (!fetchChequeLeaves) return [];
|
|
438
|
+
const res = await fetchChequeLeaves(search, page);
|
|
439
|
+
return res.is_success && res.result ? res.result : [];
|
|
440
|
+
}}
|
|
441
|
+
option={{
|
|
442
|
+
label: "cheque_number",
|
|
443
|
+
value: "cheque_number",
|
|
444
|
+
renderOption: (item: any) => (
|
|
445
|
+
<div className="flex flex-col">
|
|
446
|
+
<span>{item.cheque_number}</span>
|
|
447
|
+
{item.cheque_book?.bank_account?.bank_name && (
|
|
448
|
+
<span className="text-[11px] text-foreground-subtle mt-0.5">
|
|
449
|
+
{item.cheque_book.bank_account.bank_name} {item.cheque_book.bank_account.branch ? `— ${item.cheque_book.bank_account.branch}` : ""}
|
|
450
|
+
</span>
|
|
451
|
+
)}
|
|
452
|
+
</div>
|
|
453
|
+
)
|
|
454
|
+
}}
|
|
455
|
+
placeholder="Search cheque number..."
|
|
456
|
+
disabled={field.readonly}
|
|
457
|
+
/>
|
|
458
|
+
{entry.metadata._display_bank_name && (
|
|
459
|
+
<div className="text-[11px] text-foreground-subtle mt-1 flex items-center gap-1.5 font-medium px-1">
|
|
460
|
+
<Building size={12} className="text-foreground-disabled" />
|
|
461
|
+
{entry.metadata._display_bank_name} {entry.metadata._display_branch ? `— ${entry.metadata._display_branch}` : ""}
|
|
462
|
+
</div>
|
|
463
|
+
)}
|
|
464
|
+
</>
|
|
426
465
|
) : (
|
|
427
466
|
<input type={field.type === "number" ? "number" : field.type === "date" ? "date" : "text"}
|
|
428
467
|
{...(field.type === "number" ? { step: "any" } : {})}
|
|
@@ -432,113 +471,7 @@ function PaymentEntryRow({ entry, mode, bankAccounts, materialTypes = [], onUpda
|
|
|
432
471
|
className={cn("w-full bg-surface-1 border-[1.5px] border-border-subtle rounded-[8px] px-2.5 py-2.5 text-[12px] text-foreground-1 outline-none focus:border-primary/40 transition-all [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none", field.readonly && "opacity-60 bg-surface-0 cursor-not-allowed border-border")} />
|
|
433
472
|
)}
|
|
434
473
|
</div>
|
|
435
|
-
))}
|
|
436
|
-
</div>
|
|
437
|
-
)}
|
|
438
|
-
</div>
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/* ── ChequeAutocomplete ── */
|
|
443
|
-
|
|
444
|
-
function ChequeAutocomplete({
|
|
445
|
-
value,
|
|
446
|
-
onChange,
|
|
447
|
-
fetchChequeLeaves,
|
|
448
|
-
readonly
|
|
449
|
-
}: {
|
|
450
|
-
value: string;
|
|
451
|
-
onChange: (val: string, leafId?: number, suggestion?: any) => void;
|
|
452
|
-
fetchChequeLeaves: (q: string) => Promise<{ is_success: boolean; result?: any[] }>;
|
|
453
|
-
readonly?: boolean;
|
|
454
|
-
}) {
|
|
455
|
-
const [query, setQuery] = useState(value);
|
|
456
|
-
const [suggestions, setSuggestions] = useState<any[]>([]);
|
|
457
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
458
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
459
|
-
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
460
|
-
|
|
461
|
-
useEffect(() => {
|
|
462
|
-
setQuery(value);
|
|
463
|
-
}, [value]);
|
|
464
|
-
|
|
465
|
-
useEffect(() => {
|
|
466
|
-
const handleClickOutside = (e: MouseEvent) => {
|
|
467
|
-
if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) {
|
|
468
|
-
setIsOpen(false);
|
|
469
|
-
}
|
|
470
|
-
};
|
|
471
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
472
|
-
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
473
|
-
}, []);
|
|
474
|
-
|
|
475
|
-
const fetchSuggestions = useCallback(async (search: string) => {
|
|
476
|
-
setIsLoading(true);
|
|
477
|
-
try {
|
|
478
|
-
const res = await fetchChequeLeaves(search);
|
|
479
|
-
if (res.is_success && res.result) {
|
|
480
|
-
setSuggestions(res.result);
|
|
481
|
-
setIsOpen(true);
|
|
482
|
-
}
|
|
483
|
-
} catch (e) {
|
|
484
|
-
console.error(e);
|
|
485
|
-
} finally {
|
|
486
|
-
setIsLoading(false);
|
|
487
|
-
}
|
|
488
|
-
}, [fetchChequeLeaves]);
|
|
489
|
-
|
|
490
|
-
useEffect(() => {
|
|
491
|
-
if (!isOpen) return;
|
|
492
|
-
const timer = setTimeout(() => {
|
|
493
|
-
fetchSuggestions(query);
|
|
494
|
-
}, 300);
|
|
495
|
-
return () => clearTimeout(timer);
|
|
496
|
-
}, [query, isOpen, fetchSuggestions]);
|
|
497
|
-
|
|
498
|
-
return (
|
|
499
|
-
<div ref={wrapperRef} className="relative">
|
|
500
|
-
<input
|
|
501
|
-
type="text"
|
|
502
|
-
value={query}
|
|
503
|
-
onChange={(e) => {
|
|
504
|
-
setQuery(e.target.value);
|
|
505
|
-
onChange(e.target.value, undefined, undefined); // reset ID when typed manually
|
|
506
|
-
setIsOpen(true);
|
|
507
|
-
}}
|
|
508
|
-
onFocus={() => {
|
|
509
|
-
if (!readonly) {
|
|
510
|
-
setIsOpen(true);
|
|
511
|
-
fetchSuggestions(query);
|
|
512
|
-
}
|
|
513
|
-
}}
|
|
514
|
-
readOnly={readonly}
|
|
515
|
-
className={cn("w-full bg-surface-1 border-[1.5px] border-border-subtle rounded-[8px] px-2.5 py-2.5 text-[12px] text-foreground-1 outline-none focus:border-primary/40 transition-all", readonly && "opacity-60 bg-surface-0 cursor-not-allowed border-border")}
|
|
516
|
-
placeholder="Type to search cheques..."
|
|
517
|
-
/>
|
|
518
|
-
{isLoading && (
|
|
519
|
-
<div className="absolute right-2 top-2.5">
|
|
520
|
-
<Loader2 size={14} className="animate-spin text-foreground-subtle" />
|
|
521
|
-
</div>
|
|
522
|
-
)}
|
|
523
|
-
{isOpen && suggestions.length > 0 && !readonly && (
|
|
524
|
-
<div className="absolute z-50 mt-1 w-full bg-surface-0 border border-border-subtle rounded-[8px] shadow-lg max-h-48 overflow-y-auto">
|
|
525
|
-
{suggestions.map((s) => (
|
|
526
|
-
<div
|
|
527
|
-
key={s.id}
|
|
528
|
-
className="px-3 py-2 text-[12px] text-foreground-1 hover:bg-primary/5 hover:text-primary cursor-pointer transition-colors"
|
|
529
|
-
onClick={() => {
|
|
530
|
-
setQuery(s.cheque_number);
|
|
531
|
-
onChange(s.cheque_number, s.id, s);
|
|
532
|
-
setIsOpen(false);
|
|
533
|
-
}}
|
|
534
|
-
>
|
|
535
|
-
<div className="font-semibold">{s.cheque_number}</div>
|
|
536
|
-
<div className="text-[10px] text-foreground-subtle flex items-center gap-2">
|
|
537
|
-
{s.cheque_book?.bank_account?.bank_name && <span>{s.cheque_book.bank_account.bank_name}</span>}
|
|
538
|
-
{s.amount && <span>Amount: {s.amount}</span>}
|
|
539
|
-
</div>
|
|
540
|
-
</div>
|
|
541
|
-
))}
|
|
474
|
+
)})}
|
|
542
475
|
</div>
|
|
543
476
|
)}
|
|
544
477
|
</div>
|
|
@@ -48,7 +48,8 @@ export interface BarChartProps {
|
|
|
48
48
|
colors?: string[];
|
|
49
49
|
horizontal?: boolean;
|
|
50
50
|
stacked?: boolean;
|
|
51
|
-
|
|
51
|
+
xFormatter?: (val: number) => string;
|
|
52
|
+
yFormatter?: (val: number | string) => string;
|
|
52
53
|
tooltipYFormatter?: (val: number) => string;
|
|
53
54
|
showToolbar?: boolean;
|
|
54
55
|
showLegend?: boolean;
|
|
@@ -99,7 +100,7 @@ export function AreaChart({
|
|
|
99
100
|
grid: defaultGrid,
|
|
100
101
|
tooltip: {
|
|
101
102
|
theme: "dark", shared: true, intersect: false,
|
|
102
|
-
|
|
103
|
+
...(tooltipXFormatter ? { x: { formatter: tooltipXFormatter } } : {}),
|
|
103
104
|
y: { formatter: tooltipYFormatter || yFormatter },
|
|
104
105
|
style: { fontSize: "12px" },
|
|
105
106
|
},
|
|
@@ -162,7 +163,7 @@ export function LineChart({
|
|
|
162
163
|
export function BarChart({
|
|
163
164
|
series, categories, height = 350, colors,
|
|
164
165
|
horizontal = false, stacked = false,
|
|
165
|
-
yFormatter = (v) => String(v),
|
|
166
|
+
xFormatter, yFormatter = (v) => String(v),
|
|
166
167
|
tooltipYFormatter,
|
|
167
168
|
showToolbar = false, showLegend = true,
|
|
168
169
|
borderRadius = 6, columnWidth = "50%", className = "",
|
|
@@ -179,7 +180,7 @@ export function BarChart({
|
|
|
179
180
|
dataLabels: { enabled: false },
|
|
180
181
|
xaxis: {
|
|
181
182
|
categories,
|
|
182
|
-
labels: { style: defaultAxisStyle },
|
|
183
|
+
labels: { style: defaultAxisStyle, formatter: xFormatter },
|
|
183
184
|
axisBorder: { show: false }, axisTicks: { show: false },
|
|
184
185
|
},
|
|
185
186
|
yaxis: { labels: { style: defaultAxisStyle, formatter: yFormatter } },
|
|
@@ -220,3 +221,55 @@ export function DonutChart({
|
|
|
220
221
|
</div>
|
|
221
222
|
);
|
|
222
223
|
}
|
|
224
|
+
|
|
225
|
+
// ── Heatmap Chart ────────────────────────────────────────────────────
|
|
226
|
+
export interface HeatmapChartProps {
|
|
227
|
+
series: { name: string; data: { x: string; y: number }[] }[];
|
|
228
|
+
height?: number;
|
|
229
|
+
colors?: string[];
|
|
230
|
+
className?: string;
|
|
231
|
+
tooltipYFormatter?: (val: number) => string;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function HeatmapChart({
|
|
235
|
+
series, height = 350, colors = ["#3D5A80"], className = "", tooltipYFormatter
|
|
236
|
+
}: HeatmapChartProps) {
|
|
237
|
+
const options = useMemo<ApexCharts.ApexOptions>(() => ({
|
|
238
|
+
chart: { type: "heatmap", height, fontFamily: defaultFont, ...defaultAnimations, toolbar: { show: false } },
|
|
239
|
+
colors,
|
|
240
|
+
plotOptions: {
|
|
241
|
+
heatmap: {
|
|
242
|
+
shadeIntensity: 0.5,
|
|
243
|
+
radius: 4,
|
|
244
|
+
useFillColorAsStroke: false,
|
|
245
|
+
colorScale: {
|
|
246
|
+
ranges: [
|
|
247
|
+
{ from: 0, to: 0, color: "#f1f5f9", name: "0" },
|
|
248
|
+
{ from: 1, to: 5, color: "#cbd5e1", name: "1-5" },
|
|
249
|
+
{ from: 6, to: 15, color: "#94a3b8", name: "6-15" },
|
|
250
|
+
{ from: 16, to: 50, color: "#64748b", name: "16-50" },
|
|
251
|
+
{ from: 51, to: 1000, color: colors[0], name: "50+" }
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
dataLabels: { enabled: false },
|
|
257
|
+
xaxis: {
|
|
258
|
+
labels: { style: defaultAxisStyle },
|
|
259
|
+
axisBorder: { show: false }, axisTicks: { show: false }
|
|
260
|
+
},
|
|
261
|
+
yaxis: { labels: { style: defaultAxisStyle } },
|
|
262
|
+
grid: defaultGrid,
|
|
263
|
+
tooltip: {
|
|
264
|
+
theme: "dark",
|
|
265
|
+
y: { formatter: tooltipYFormatter || ((val) => String(val)) },
|
|
266
|
+
style: { fontSize: "12px" },
|
|
267
|
+
},
|
|
268
|
+
}), [colors, height, tooltipYFormatter]);
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<div className={`w-full ${className}`}>
|
|
272
|
+
<ApexChart options={options} series={series} type="heatmap" height={height} width="100%" />
|
|
273
|
+
</div>
|
|
274
|
+
);
|
|
275
|
+
}
|