@apptimate/ui 2.0.0 → 2.2.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@apptimate/ui",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "main": "src/index.tsx",
5
5
  "types": "src/index.tsx",
6
6
  "dependencies": {
@@ -9,8 +9,9 @@ import { CreditCard, Banknote, Building, Trash2, Loader2, Split, Check, AlertCir
9
9
  export interface PaymentModeField {
10
10
  key: string;
11
11
  label: string;
12
- type: "text" | "number" | "date" | "bank_account_select";
12
+ type: "text" | "number" | "date" | "bank_account_select" | "material_type_select";
13
13
  required: boolean;
14
+ readonly?: boolean;
14
15
  }
15
16
 
16
17
  export interface PaymentMode {
@@ -56,11 +57,15 @@ export interface PaymentSectionProps {
56
57
  fetchPaymentModes: () => Promise<{ is_success: boolean; result?: any }>;
57
58
  /** Fetcher for bank accounts */
58
59
  fetchBankAccounts: () => Promise<{ is_success: boolean; result?: any }>;
60
+ /** Optional array of material types for the material_type_select field */
61
+ materialTypes?: any[];
62
+ /** Special mode codes that are allowed (e.g. 'credit-gold') */
63
+ allowedSpecialModes?: string[];
59
64
  }
60
65
 
61
66
  /* ── Component ── */
62
67
 
63
- export function PaymentSection({ totalAmount, payments, onPaymentsChange, compact, fetchPaymentModes, fetchBankAccounts }: PaymentSectionProps) {
68
+ export function PaymentSection({ totalAmount, payments, onPaymentsChange, compact, fetchPaymentModes, fetchBankAccounts, materialTypes = [], allowedSpecialModes = [] }: PaymentSectionProps) {
64
69
  const [paymentModes, setPaymentModes] = useState<PaymentMode[]>([]);
65
70
  const [bankAccounts, setBankAccounts] = useState<BankAccount[]>([]);
66
71
  const [isLoading, setIsLoading] = useState(false);
@@ -72,7 +77,9 @@ export function PaymentSection({ totalAmount, payments, onPaymentsChange, compac
72
77
  setIsLoading(true);
73
78
  try {
74
79
  const [modesRes, bankRes] = await Promise.all([fetchPaymentModes(), fetchBankAccounts()]);
75
- const modes: PaymentMode[] = modesRes.is_success ? (modesRes.result || []) : [];
80
+ const modesRaw: PaymentMode[] = modesRes.is_success ? (modesRes.result || []) : [];
81
+ const SPECIAL_MODES = ["credit-gold"];
82
+ const modes = modesRaw.filter((m) => !SPECIAL_MODES.includes(m.code) || allowedSpecialModes.includes(m.code));
76
83
  setPaymentModes(modes);
77
84
  if (bankRes.is_success) setBankAccounts(bankRes.result || []);
78
85
 
@@ -98,12 +105,12 @@ export function PaymentSection({ totalAmount, payments, onPaymentsChange, compac
98
105
  const balance = useMemo(() => totalAmount - totalPaid, [totalAmount, totalPaid]);
99
106
  const formatCurrency = (v: number) => v.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
100
107
 
101
- // ── Mode icon ──
102
108
  const getModeIcon = useCallback((code: string, size = 14) => {
103
109
  switch (code) {
104
110
  case "cash": return <Banknote size={size} />;
105
111
  case "card": return <CreditCard size={size} />;
106
112
  case "bank_transfer": return <Building size={size} />;
113
+ case "credit-gold": return <AlertCircle size={size} />; // Fallback, better to use something else if available
107
114
  default: return <CreditCard size={size} />;
108
115
  }
109
116
  }, []);
@@ -219,7 +226,7 @@ export function PaymentSection({ totalAmount, payments, onPaymentsChange, compac
219
226
  {/* ── Single Mode: Entry with fields (no delete) ── */}
220
227
  {selectionMode === "single" && payments.length === 1 && (
221
228
  <PaymentEntryRow entry={payments[0]} mode={paymentModes.find((m) => m.id === payments[0].mode_id)}
222
- bankAccounts={bankAccounts} onUpdate={(updates) => updatePayment(0, updates)} onRemove={() => {}} showRemove={false} />
229
+ bankAccounts={bankAccounts} materialTypes={materialTypes} onUpdate={(updates) => updatePayment(0, updates)} onRemove={() => {}} showRemove={false} />
223
230
  )}
224
231
 
225
232
  {/* ── Split Mode: Multi-entry UI ── */}
@@ -228,7 +235,7 @@ export function PaymentSection({ totalAmount, payments, onPaymentsChange, compac
228
235
  {payments.map((entry, idx) => {
229
236
  const mode = paymentModes.find((m) => m.id === entry.mode_id);
230
237
  return (
231
- <PaymentEntryRow key={idx} entry={entry} mode={mode} bankAccounts={bankAccounts}
238
+ <PaymentEntryRow key={idx} entry={entry} mode={mode} bankAccounts={bankAccounts} materialTypes={materialTypes}
232
239
  onUpdate={(updates) => updatePayment(idx, updates)} onRemove={() => removePayment(idx)} />
233
240
  );
234
241
  })}
@@ -299,12 +306,13 @@ interface PaymentEntryRowProps {
299
306
  entry: PaymentEntry;
300
307
  mode?: PaymentMode;
301
308
  bankAccounts: BankAccount[];
309
+ materialTypes?: any[];
302
310
  onUpdate: (updates: Partial<PaymentEntry>) => void;
303
311
  onRemove: () => void;
304
312
  showRemove?: boolean;
305
313
  }
306
314
 
307
- function PaymentEntryRow({ entry, mode, bankAccounts, onUpdate, onRemove, showRemove = true }: PaymentEntryRowProps) {
315
+ function PaymentEntryRow({ entry, mode, bankAccounts, materialTypes = [], onUpdate, onRemove, showRemove = true }: PaymentEntryRowProps) {
308
316
  const fields = mode?.field_config?.fields || [];
309
317
 
310
318
  const getModeIcon = () => {
@@ -312,6 +320,7 @@ function PaymentEntryRow({ entry, mode, bankAccounts, onUpdate, onRemove, showRe
312
320
  case "cash": return <Banknote size={13} />;
313
321
  case "card": return <CreditCard size={13} />;
314
322
  case "bank_transfer": return <Building size={13} />;
323
+ case "credit-gold": return <AlertCircle size={13} />;
315
324
  default: return <CreditCard size={13} />;
316
325
  }
317
326
  };
@@ -352,15 +361,26 @@ function PaymentEntryRow({ entry, mode, bankAccounts, onUpdate, onRemove, showRe
352
361
  {field.type === "bank_account_select" ? (
353
362
  <select value={entry.metadata[field.key] || ""}
354
363
  onChange={(e) => onUpdate({ metadata: { ...entry.metadata, [field.key]: e.target.value ? Number(e.target.value) : "" } })}
355
- className="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">
364
+ disabled={field.readonly}
365
+ className="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 disabled:opacity-50 disabled:cursor-not-allowed">
356
366
  <option value="">Select...</option>
357
367
  {bankAccounts.map((b) => (<option key={b.id} value={b.id}>{b.bank_name} - {b.account_number}</option>))}
358
368
  </select>
369
+ ) : field.type === "material_type_select" ? (
370
+ <select value={entry.metadata[field.key] || ""}
371
+ onChange={(e) => onUpdate({ metadata: { ...entry.metadata, [field.key]: e.target.value ? Number(e.target.value) : "" } })}
372
+ disabled={field.readonly}
373
+ className="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 disabled:opacity-50 disabled:cursor-not-allowed">
374
+ <option value="">Select Material...</option>
375
+ {materialTypes.map((m) => (<option key={m.id} value={m.id}>{m.name}</option>))}
376
+ </select>
359
377
  ) : (
360
378
  <input type={field.type === "number" ? "number" : field.type === "date" ? "date" : "text"}
379
+ {...(field.type === "number" ? { step: "any" } : {})}
361
380
  value={entry.metadata[field.key] || ""}
362
381
  onChange={(e) => onUpdate({ metadata: { ...entry.metadata, [field.key]: e.target.value } })}
363
- className="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" />
382
+ readOnly={field.readonly}
383
+ 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")} />
364
384
  )}
365
385
  </div>
366
386
  ))}