@apptimate/ui 6.3.0 → 6.4.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": "6.3.0",
3
+ "version": "6.4.0",
4
4
  "main": "src/index.tsx",
5
5
  "types": "src/index.tsx",
6
6
  "dependencies": {
@@ -57,6 +57,8 @@ export interface SearchableSelectProps<T = Record<string, unknown>> {
57
57
  allowClear?: boolean;
58
58
  /** Disabled state */
59
59
  disabled?: boolean;
60
+ /** Required state */
61
+ required?: boolean;
60
62
  /** Label text above the select */
61
63
  label?: string;
62
64
  /** Error message below the select */
@@ -405,6 +405,7 @@ function PaymentEntryRow({ entry, mode, bankAccounts, materialTypes = [], onUpda
405
405
  <select value={entry.metadata[field.key] || ""}
406
406
  onChange={(e) => onUpdate({ metadata: { ...entry.metadata, [field.key]: e.target.value ? Number(e.target.value) : "" } })}
407
407
  disabled={field.readonly}
408
+ required={field.required}
408
409
  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">
409
410
  <option value="">Select...</option>
410
411
  {bankAccounts.map((b) => (<option key={b.id} value={b.id}>{b.bank_name} - {b.account_number}</option>))}
@@ -413,6 +414,7 @@ function PaymentEntryRow({ entry, mode, bankAccounts, materialTypes = [], onUpda
413
414
  <select value={entry.metadata[field.key] || ""}
414
415
  onChange={(e) => onUpdate({ metadata: { ...entry.metadata, [field.key]: e.target.value ? Number(e.target.value) : "" } })}
415
416
  disabled={field.readonly}
417
+ required={field.required}
416
418
  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">
417
419
  <option value="">Select Material...</option>
418
420
  {materialTypes.map((m) => (<option key={m.id} value={m.id}>{m.name}</option>))}
@@ -421,6 +423,7 @@ function PaymentEntryRow({ entry, mode, bankAccounts, materialTypes = [], onUpda
421
423
  <select value={entry.metadata[field.key] || ""}
422
424
  onChange={(e) => onUpdate({ metadata: { ...entry.metadata, [field.key]: e.target.value } })}
423
425
  disabled={field.readonly}
426
+ required={field.required}
424
427
  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">
425
428
  <option value="">Select...</option>
426
429
  {field.options?.map((o) => (<option key={o.value} value={o.value}>{o.label}</option>))}
@@ -445,6 +448,7 @@ function PaymentEntryRow({ entry, mode, bankAccounts, materialTypes = [], onUpda
445
448
  });
446
449
  }}
447
450
  disabled={field.readonly}
451
+ required={field.required}
448
452
  placeholder="Select..."
449
453
  loadOptions={async () => field.options || []}
450
454
  />
@@ -488,6 +492,7 @@ function PaymentEntryRow({ entry, mode, bankAccounts, materialTypes = [], onUpda
488
492
  }}
489
493
  placeholder="Search cheque number..."
490
494
  disabled={field.readonly}
495
+ required={field.required}
491
496
  />
492
497
  {entry.metadata._display_bank_name && (
493
498
  <div className="text-[11px] text-foreground-subtle mt-1 flex items-center gap-1.5 font-medium px-1">
@@ -502,6 +507,7 @@ function PaymentEntryRow({ entry, mode, bankAccounts, materialTypes = [], onUpda
502
507
  value={entry.metadata[field.key] || ""}
503
508
  onChange={(e) => onUpdate({ metadata: { ...entry.metadata, [field.key]: e.target.value } })}
504
509
  readOnly={field.readonly}
510
+ required={field.required}
505
511
  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")} />
506
512
  )}
507
513
  </div>
@@ -13,6 +13,7 @@ interface SerialSelectionModalProps {
13
13
  variant: TransactionItemVariant | null;
14
14
  allowCreate?: boolean;
15
15
  initialSerials?: string[];
16
+ initialSerialObjects?: {id: number, serial_number: string}[];
16
17
  warehouseId?: number;
17
18
  onConfirm: (serials: string[], serialObjects?: {id: number, serial_number: string}[]) => void;
18
19
  }
@@ -24,10 +25,12 @@ export function SerialSelectionModal({
24
25
  variant,
25
26
  allowCreate = false,
26
27
  initialSerials = [],
28
+ initialSerialObjects = [],
27
29
  warehouseId,
28
30
  onConfirm
29
31
  }: SerialSelectionModalProps) {
30
32
  const [serials, setSerials] = useState<string[]>([]);
33
+ const [selectedSerialObjects, setSelectedSerialObjects] = useState<{id: number, serial_number: string}[]>([]);
31
34
  const [inputValue, setInputValue] = useState("");
32
35
  const [availableSerials, setAvailableSerials] = useState<any[]>([]);
33
36
  const [isLoading, setIsLoading] = useState(false);
@@ -35,6 +38,7 @@ export function SerialSelectionModal({
35
38
  useEffect(() => {
36
39
  if (isOpen) {
37
40
  setSerials(initialSerials);
41
+ setSelectedSerialObjects(initialSerialObjects);
38
42
  setInputValue("");
39
43
  }
40
44
  }, [isOpen, item, allowCreate]);
@@ -81,6 +85,7 @@ export function SerialSelectionModal({
81
85
 
82
86
  const handleAddSerial = (e?: React.FormEvent) => {
83
87
  e?.preventDefault();
88
+ e?.stopPropagation();
84
89
  // Allow comma or space separated parsing
85
90
  const rawTokens = inputValue.split(/[\s,]+/);
86
91
  let addedCount = 0;
@@ -106,11 +111,13 @@ export function SerialSelectionModal({
106
111
  setSerials(serials.filter(v => v !== s));
107
112
  };
108
113
 
109
- const toggleSelectSerial = (s: string) => {
110
- if (serials.includes(s)) {
111
- setSerials(serials.filter(v => v !== s));
114
+ const toggleSelectSerial = (s: any) => {
115
+ if (serials.includes(s.serial_number)) {
116
+ setSerials(serials.filter(v => v !== s.serial_number));
117
+ setSelectedSerialObjects(prev => prev.filter(v => v.serial_number !== s.serial_number));
112
118
  } else {
113
- setSerials([...serials, s]);
119
+ setSerials([...serials, s.serial_number]);
120
+ setSelectedSerialObjects(prev => [...prev, { id: s.id, serial_number: s.serial_number }]);
114
121
  }
115
122
  };
116
123
 
@@ -121,10 +128,9 @@ export function SerialSelectionModal({
121
128
  }
122
129
 
123
130
  if (!allowCreate) {
124
- const serialObjects = availableSerials
125
- .filter(s => serials.includes(s.serial_number))
126
- .map(s => ({ id: s.id, serial_number: s.serial_number }));
127
- onConfirm(serials, serialObjects);
131
+ // Ensure we only pass objects that are currently in the selected serials array
132
+ const finalObjects = selectedSerialObjects.filter(obj => serials.includes(obj.serial_number));
133
+ onConfirm(serials, finalObjects);
128
134
  } else {
129
135
  onConfirm(serials);
130
136
  }
@@ -172,7 +178,11 @@ export function SerialSelectionModal({
172
178
 
173
179
  <div className="flex flex-col border border-border-subtle rounded-xl overflow-hidden bg-surface-0 min-h-[250px] shadow-sm">
174
180
  <div className="p-3 border-b border-border-subtle bg-surface-hover">
175
- <form onSubmit={handleAddSerial} className="flex gap-2">
181
+ <form
182
+ onSubmit={handleAddSerial}
183
+ onKeyDown={(e) => e.key === 'Enter' && e.stopPropagation()}
184
+ className="flex gap-2"
185
+ >
176
186
  <input
177
187
  type="text"
178
188
  autoFocus
@@ -233,7 +243,7 @@ export function SerialSelectionModal({
233
243
  type="checkbox"
234
244
  className="w-4 h-4 rounded border-border-subtle text-primary focus:ring-primary"
235
245
  checked={serials.includes(s.serial_number)}
236
- onChange={() => toggleSelectSerial(s.serial_number)}
246
+ onChange={() => toggleSelectSerial(s)}
237
247
  />
238
248
  <span className="font-mono text-[13px] text-foreground-1">{s.serial_number}</span>
239
249
  </label>
@@ -155,7 +155,7 @@ export function MetadataPanel({
155
155
  onChange={(p) => onPartyChange(p?.id ?? null, p ? { id: p.id, name: p.name, code: p.code, type: p.type } : undefined)}
156
156
  label={config.partyLabel}
157
157
  partyType={config.partyType}
158
- isRequired
158
+ isRequired={config.type !== 'pos'}
159
159
  disabled={config.disablePartySelection}
160
160
  />
161
161
  )
@@ -248,8 +248,8 @@ export function MetadataPanel({
248
248
 
249
249
  {/* ── Submit Button ── */}
250
250
  <Button
251
+ type="submit"
251
252
  color="primary"
252
- onClick={onSubmit}
253
253
  isDisabled={lineItems.length === 0}
254
254
  isLoading={isSubmitting}
255
255
  className="!w-full !py-4 !text-[14px] !font-semibold !rounded-[10px]"