@apptimate/ui 6.5.0 → 6.7.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.5.0",
3
+ "version": "6.7.0",
4
4
  "main": "src/index.tsx",
5
5
  "types": "src/index.tsx",
6
6
  "dependencies": {
@@ -371,7 +371,6 @@ export default function ItemFormWizard({
371
371
  </div>
372
372
  </div>
373
373
 
374
- {formData.has_stocks && (
375
374
  <div className="p-4 bg-gray-50/50 rounded-xl border border-gray-100">
376
375
  <p className="text-[11px] font-bold text-gray-400 uppercase tracking-wider mb-3">Tracking Options</p>
377
376
  <div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 sm:gap-6">
@@ -411,7 +410,6 @@ export default function ItemFormWizard({
411
410
  </div>
412
411
 
413
412
  </div>
414
- )}
415
413
 
416
414
  {formData.has_stocks && formData.tracking_type !== 'serial' && (
417
415
  <div className="p-4 bg-gray-50/50 rounded-xl border border-gray-100">
@@ -51,18 +51,45 @@ export function PartyPicker({
51
51
  const [quickAddEmail, setQuickAddEmail] = useState("");
52
52
  const [quickAddPhone, setQuickAddPhone] = useState("");
53
53
  const [quickAddSecondaryContact, setQuickAddSecondaryContact] = useState("");
54
- const [quickAddGender, setQuickAddGender] = useState("");
55
54
  const [quickAddDOB, setQuickAddDOB] = useState("");
55
+ const [quickAddIdentityType, setQuickAddIdentityType] = useState("");
56
+ const [quickAddIdentity, setQuickAddIdentity] = useState("");
57
+
58
+ const [quickAddAddressLine1, setQuickAddAddressLine1] = useState("");
59
+ const [quickAddAddressLine2, setQuickAddAddressLine2] = useState("");
60
+ const [quickAddCity, setQuickAddCity] = useState("");
61
+ const [quickAddState, setQuickAddState] = useState("");
62
+ const [quickAddPostalCode, setQuickAddPostalCode] = useState("");
63
+ const [quickAddCountry, setQuickAddCountry] = useState("");
64
+ const [showQuickAddDetailedAddress, setShowQuickAddDetailedAddress] = useState(false);
65
+
56
66
  const [quickAddCurrencyId, setQuickAddCurrencyId] = useState<string | number | undefined>(undefined);
57
67
  const [quickAddCurrencyDisplay, setQuickAddCurrencyDisplay] = useState<any>(null);
58
68
  const [errors, setErrors] = useState<Record<string, string[]>>({});
59
69
  const [isCreating, setIsCreating] = useState(false);
60
70
 
71
+ const [fieldConfigs, setFieldConfigs] = useState<any[]>([]);
72
+
73
+ const effectiveType = partyType === "all" ? "customer" : partyType;
74
+ const mergedConfig = React.useMemo(() => {
75
+ const configMap: Record<string, { active: boolean; mandatory: boolean }> = {};
76
+ const relevantConfigs = fieldConfigs.filter(c => c.entity === effectiveType);
77
+ for (const config of relevantConfigs) {
78
+ if (!configMap[config.field]) {
79
+ configMap[config.field] = { active: false, mandatory: false };
80
+ }
81
+ if (config.status === "active") configMap[config.field].active = true;
82
+ if (config.is_mandatory) configMap[config.field].mandatory = true;
83
+ }
84
+ return configMap;
85
+ }, [fieldConfigs, effectiveType]);
86
+
61
87
  const hasInitializedQuickAddCurrency = useRef(false);
62
88
 
63
89
  useEffect(() => {
64
- if (isQuickAddOpen && !hasInitializedQuickAddCurrency.current) {
65
- hasInitializedQuickAddCurrency.current = true;
90
+ if (isQuickAddOpen) {
91
+ if (!hasInitializedQuickAddCurrency.current) {
92
+ hasInitializedQuickAddCurrency.current = true;
66
93
  sendRequest({ url: `${process.env.NEXT_PUBLIC_API_URL}/api/settings`, method: 'GET' })
67
94
  .then((res: any) => {
68
95
  if (res.responseData?.is_success && res.responseData?.result) {
@@ -89,8 +116,18 @@ export function PartyPicker({
89
116
  }
90
117
  }
91
118
  });
119
+ }
120
+
121
+ if (fieldConfigs.length === 0) {
122
+ sendRequest({ url: `${process.env.NEXT_PUBLIC_API_URL}/api/field-configs`, method: 'GET' })
123
+ .then((res: any) => {
124
+ if (res.responseData?.is_success && res.responseData?.result) {
125
+ setFieldConfigs(res.responseData.result);
126
+ }
127
+ });
128
+ }
92
129
  }
93
- }, [isQuickAddOpen]);
130
+ }, [isQuickAddOpen, fieldConfigs.length]);
94
131
 
95
132
  const fetchData = useCallback(async (query: string = "") => {
96
133
  setIsLoading(true);
@@ -121,12 +158,17 @@ export function PartyPicker({
121
158
 
122
159
  const handleSelect = (party: any) => {
123
160
  onChange({
124
- id: party.id,
125
- name: party.name,
126
- code: party.code,
127
- type: party.type,
128
- default_currency_id: party.default_currency_id,
129
- default_currency: party.default_currency,
161
+ id: Number(party.id),
162
+ name: String(party.name || ""),
163
+ code: party.code ? String(party.code) : undefined,
164
+ type: Array.isArray(party.type) ? party.type[0] : (party.type ? String(party.type) : undefined),
165
+ default_currency_id: party.default_currency_id ? Number(party.default_currency_id) : undefined,
166
+ default_currency: party.default_currency ? {
167
+ id: Number(party.default_currency.id),
168
+ code: String(party.default_currency.code),
169
+ name: String(party.default_currency.name),
170
+ symbol: String(party.default_currency.symbol),
171
+ } : undefined,
130
172
  });
131
173
  setIsOpen(false);
132
174
  };
@@ -146,11 +188,20 @@ export function PartyPicker({
146
188
  email: quickAddEmail.trim() || undefined,
147
189
  primary_contact: quickAddPhone.trim() || undefined,
148
190
  secondary_contact: quickAddSecondaryContact.trim() || undefined,
149
- gender: quickAddGender || undefined,
150
191
  date_of_birth: quickAddDOB || undefined,
192
+ identity_type: quickAddIdentityType || undefined,
193
+ identity: quickAddIdentity.trim() || undefined,
151
194
  default_currency_id: quickAddCurrencyId || undefined,
152
- type: [partyType === "all" ? "customer" : partyType],
153
- status: "active"
195
+ type: [partyType === "all" || !partyType ? "customer" : partyType],
196
+ status: "active",
197
+ address: quickAddAddressLine1.trim() ? {
198
+ address_line_1: quickAddAddressLine1.trim(),
199
+ address_line_2: quickAddAddressLine2.trim() || undefined,
200
+ city: quickAddCity.trim() || undefined,
201
+ state: quickAddState.trim() || undefined,
202
+ postal_code: quickAddPostalCode.trim() || undefined,
203
+ country: quickAddCountry.trim() || undefined,
204
+ } : undefined
154
205
  };
155
206
  const res = await createParty(payload);
156
207
  if (res.is_success && res.result) {
@@ -158,16 +209,16 @@ export function PartyPicker({
158
209
  const party = res.result;
159
210
  const partyName = party.name || party.full_name || [party.first_name, party.last_name].filter(Boolean).join(" ");
160
211
  onChange({
161
- id: party.id,
162
- name: partyName,
163
- code: party.code,
164
- type: party.type,
165
- default_currency_id: quickAddCurrencyDisplay?.id,
212
+ id: Number(party.id),
213
+ name: String(partyName),
214
+ code: party.code ? String(party.code) : undefined,
215
+ type: Array.isArray(party.type) ? party.type[0] : (party.type ? String(party.type) : undefined),
216
+ default_currency_id: quickAddCurrencyDisplay?.id ? Number(quickAddCurrencyDisplay.id) : undefined,
166
217
  default_currency: quickAddCurrencyDisplay ? {
167
- id: quickAddCurrencyDisplay.id,
168
- code: quickAddCurrencyDisplay.code,
169
- name: quickAddCurrencyDisplay.name,
170
- symbol: quickAddCurrencyDisplay.symbol
218
+ id: Number(quickAddCurrencyDisplay.id),
219
+ code: String(quickAddCurrencyDisplay.code),
220
+ name: String(quickAddCurrencyDisplay.name),
221
+ symbol: String(quickAddCurrencyDisplay.symbol)
171
222
  } : undefined
172
223
  });
173
224
  setIsQuickAddOpen(false);
@@ -176,8 +227,16 @@ export function PartyPicker({
176
227
  setQuickAddEmail("");
177
228
  setQuickAddPhone("");
178
229
  setQuickAddSecondaryContact("");
179
- setQuickAddGender("");
180
230
  setQuickAddDOB("");
231
+ setQuickAddIdentityType("");
232
+ setQuickAddIdentity("");
233
+ setQuickAddAddressLine1("");
234
+ setQuickAddAddressLine2("");
235
+ setQuickAddCity("");
236
+ setQuickAddState("");
237
+ setQuickAddPostalCode("");
238
+ setQuickAddCountry("");
239
+ setShowQuickAddDetailedAddress(false);
181
240
  setErrors({});
182
241
  setIsOpen(false);
183
242
  } else if ((res as any).errors) {
@@ -229,8 +288,16 @@ export function PartyPicker({
229
288
  setQuickAddEmail("");
230
289
  setQuickAddPhone("");
231
290
  setQuickAddSecondaryContact("");
232
- setQuickAddGender("");
233
291
  setQuickAddDOB("");
292
+ setQuickAddIdentityType("");
293
+ setQuickAddIdentity("");
294
+ setQuickAddAddressLine1("");
295
+ setQuickAddAddressLine2("");
296
+ setQuickAddCity("");
297
+ setQuickAddState("");
298
+ setQuickAddPostalCode("");
299
+ setQuickAddCountry("");
300
+ setShowQuickAddDetailedAddress(false);
234
301
  setErrors({});
235
302
  }}
236
303
  className="p-1.5 rounded-lg bg-primary-50 text-primary-600 hover:bg-primary-100 transition-colors flex-shrink-0"
@@ -256,7 +323,11 @@ export function PartyPicker({
256
323
  <PickerItem
257
324
  key={party.id}
258
325
  label={party.name}
259
- sublabel={party.code ? `Code: ${party.code}` : undefined}
326
+ sublabel={[
327
+ party.code ? `Code: ${party.code}` : null,
328
+ party.phone ? `Contact: ${party.phone}` : null,
329
+ party.identity ? `Identity: ${party.identity}` : null
330
+ ].filter(Boolean).join(" | ") || undefined}
260
331
  isSelected={String(value) === String(party.id)}
261
332
  onClick={() => handleSelect(party)}
262
333
  />
@@ -276,71 +347,181 @@ export function PartyPicker({
276
347
  >
277
348
  <div className="space-y-4">
278
349
  <div className="grid grid-cols-2 gap-3">
279
- <Input
280
- label="First Name"
281
- isRequired
282
- placeholder={`Enter ${label.toLowerCase()} first name`}
283
- value={quickAddFirstName}
284
- onChange={(e) => setQuickAddFirstName(e.target.value)}
285
- error={errors.first_name?.[0]}
286
- />
287
- <Input
288
- label="Last Name"
289
- placeholder={`Enter ${label.toLowerCase()} last name`}
290
- value={quickAddLastName}
291
- onChange={(e) => setQuickAddLastName(e.target.value)}
292
- error={errors.last_name?.[0]}
293
- />
350
+ {mergedConfig.first_name?.active !== false && (
351
+ <Input
352
+ label="First Name"
353
+ isRequired={mergedConfig.first_name?.mandatory ?? true}
354
+ placeholder={`Enter ${label.toLowerCase()} first name`}
355
+ value={quickAddFirstName}
356
+ onChange={(e) => setQuickAddFirstName(e.target.value)}
357
+ error={errors.first_name?.[0]}
358
+ />
359
+ )}
360
+ {mergedConfig.last_name?.active !== false && (
361
+ <Input
362
+ label="Last Name"
363
+ isRequired={mergedConfig.last_name?.mandatory}
364
+ placeholder={`Enter ${label.toLowerCase()} last name`}
365
+ value={quickAddLastName}
366
+ onChange={(e) => setQuickAddLastName(e.target.value)}
367
+ error={errors.last_name?.[0]}
368
+ />
369
+ )}
294
370
  </div>
295
371
  <div className="grid grid-cols-2 gap-3">
296
- <Input
297
- label="Email"
298
- type="email"
299
- placeholder="Email address"
300
- value={quickAddEmail}
301
- onChange={(e) => setQuickAddEmail(e.target.value)}
302
- error={errors.email?.[0]}
303
- />
304
- <Input
305
- label="Primary Contact"
306
- placeholder="Primary phone number"
307
- value={quickAddPhone}
308
- onChange={(e) => setQuickAddPhone(e.target.value)}
309
- error={errors.primary_contact?.[0]}
310
- />
372
+ {mergedConfig.email?.active !== false && (
373
+ <Input
374
+ label="Email"
375
+ type="email"
376
+ isRequired={mergedConfig.email?.mandatory}
377
+ placeholder="Email address"
378
+ value={quickAddEmail}
379
+ onChange={(e) => setQuickAddEmail(e.target.value)}
380
+ error={errors.email?.[0]}
381
+ />
382
+ )}
383
+ {mergedConfig.primary_contact?.active !== false && (
384
+ <Input
385
+ label="Primary Contact"
386
+ isRequired={mergedConfig.primary_contact?.mandatory}
387
+ placeholder="Primary phone number"
388
+ value={quickAddPhone}
389
+ onChange={(e) => setQuickAddPhone(e.target.value)}
390
+ error={errors.primary_contact?.[0]}
391
+ />
392
+ )}
311
393
  </div>
312
394
  <div className="grid grid-cols-2 gap-3">
313
- <Input
314
- label="Secondary Contact"
315
- placeholder="Alternative phone (optional)"
316
- value={quickAddSecondaryContact}
317
- onChange={(e) => setQuickAddSecondaryContact(e.target.value)}
318
- error={errors.secondary_contact?.[0]}
319
- />
320
- <Input
321
- label="Date of Birth"
322
- type="date"
323
- value={quickAddDOB}
324
- onChange={(e) => setQuickAddDOB(e.target.value)}
325
- error={errors.date_of_birth?.[0]}
326
- />
395
+ {mergedConfig.secondary_contact?.active !== false && (
396
+ <Input
397
+ label="Secondary Contact"
398
+ isRequired={mergedConfig.secondary_contact?.mandatory}
399
+ placeholder="Alternative phone (optional)"
400
+ value={quickAddSecondaryContact}
401
+ onChange={(e) => setQuickAddSecondaryContact(e.target.value)}
402
+ error={errors.secondary_contact?.[0]}
403
+ />
404
+ )}
405
+ {mergedConfig.date_of_birth?.active !== false && (
406
+ <Input
407
+ label="Date of Birth"
408
+ type="date"
409
+ isRequired={mergedConfig.date_of_birth?.mandatory}
410
+ value={quickAddDOB}
411
+ onChange={(e) => setQuickAddDOB(e.target.value)}
412
+ error={errors.date_of_birth?.[0]}
413
+ />
414
+ )}
327
415
  </div>
328
- <div className="grid grid-cols-2 gap-3">
329
- <div className="space-y-1">
330
- <label className="text-sm font-semibold text-gray-700">Gender</label>
416
+ {mergedConfig.identity?.active !== false && (
417
+ <div className="space-y-1.5 w-full">
418
+ <label className="text-[13px] font-medium text-[#4A5568]">
419
+ Identity Details {mergedConfig.identity?.mandatory && <span className="text-red-500">*</span>}
420
+ </label>
421
+ <div className="flex relative">
331
422
  <select
332
- value={quickAddGender}
333
- onChange={(e) => setQuickAddGender(e.target.value)}
334
- className="w-full rounded-lg border border-gray-200 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white"
423
+ value={quickAddIdentityType}
424
+ onChange={(e) => setQuickAddIdentityType(e.target.value)}
425
+ className={`w-1/3 bg-surface-0 border-[1.5px] ${errors.identity_type ? 'border-danger-alt' : 'border-border-subtle'} rounded-l-[10px] px-3.5 py-2.5 text-[13.5px] text-foreground-1 outline-none transition-all hover:border-gray-300 focus:border-gray-300 focus:bg-surface-1 border-r-0 focus:z-10 appearance-none`}
426
+ style={{ backgroundImage: 'url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23131313%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E")', backgroundRepeat: 'no-repeat', backgroundPosition: 'right .7rem top 50%', backgroundSize: '.65rem auto' }}
335
427
  >
336
- <option value="">Select gender</option>
337
- <option value="male">Male</option>
338
- <option value="female">Female</option>
428
+ <option value="">Select type</option>
429
+ <option value="nic">NIC</option>
430
+ <option value="passport">Passport</option>
431
+ <option value="driving_license">Driving License</option>
432
+ <option value="business_registration">Business Registration</option>
339
433
  <option value="other">Other</option>
340
- <option value="prefer_not_to_say">Prefer not to say</option>
341
434
  </select>
342
- {errors.gender?.[0] && <p className="text-xs text-red-500">{errors.gender[0]}</p>}
435
+ <input
436
+ type="text"
437
+ placeholder="Enter identity number"
438
+ value={quickAddIdentity}
439
+ onChange={(e) => setQuickAddIdentity(e.target.value)}
440
+ className={`flex-1 bg-surface-0 border-[1.5px] ${errors.identity ? 'border-danger-alt' : 'border-border-subtle'} rounded-r-[10px] px-3.5 py-2.5 text-[13.5px] text-foreground-1 outline-none transition-all hover:border-gray-300 focus:border-gray-300 focus:bg-surface-1 placeholder:text-foreground-disabled z-0 focus:z-10 relative`}
441
+ />
343
442
  </div>
443
+ {(errors.identity_type?.[0] || errors.identity?.[0]) && (
444
+ <span className="text-[11px] text-danger-alt font-medium">
445
+ {errors.identity_type?.[0] || errors.identity?.[0]}
446
+ </span>
447
+ )}
448
+ </div>
449
+ )}
450
+ {mergedConfig.address?.active !== false && (
451
+ <div className="space-y-3">
452
+ <div className="flex justify-between items-center">
453
+ <label className="text-[13px] font-medium text-[#4A5568]">
454
+ Address {mergedConfig.address?.mandatory ? <span className="text-red-500">*</span> : "(Optional)"}
455
+ </label>
456
+ <button
457
+ type="button"
458
+ onClick={() => setShowQuickAddDetailedAddress(!showQuickAddDetailedAddress)}
459
+ className="text-xs font-semibold text-primary-600 hover:text-primary-700 transition-colors"
460
+ >
461
+ {showQuickAddDetailedAddress ? "Quick Add Address" : "Add Detailed Address"}
462
+ </button>
463
+ </div>
464
+
465
+ {!showQuickAddDetailedAddress ? (
466
+ <Input
467
+ placeholder="Enter complete primary address..."
468
+ value={quickAddAddressLine1}
469
+ onChange={(e) => setQuickAddAddressLine1(e.target.value)}
470
+ error={errors.address?.[0] || errors['address.address_line_1']?.[0]}
471
+ />
472
+ ) : (
473
+ <div className="p-3 bg-gray-50 border border-gray-100 rounded-xl space-y-3">
474
+ <Input
475
+ label="Address Line 1"
476
+ placeholder="Street address, P.O. box, etc."
477
+ value={quickAddAddressLine1}
478
+ onChange={(e) => setQuickAddAddressLine1(e.target.value)}
479
+ error={errors.address?.[0] || errors['address.address_line_1']?.[0]}
480
+ />
481
+ <Input
482
+ label="Address Line 2"
483
+ placeholder="Apartment, suite, unit, etc. (optional)"
484
+ value={quickAddAddressLine2}
485
+ onChange={(e) => setQuickAddAddressLine2(e.target.value)}
486
+ error={errors['address.address_line_2']?.[0]}
487
+ />
488
+ <div className="grid grid-cols-2 gap-3">
489
+ <Input
490
+ label="City"
491
+ placeholder="City"
492
+ value={quickAddCity}
493
+ onChange={(e) => setQuickAddCity(e.target.value)}
494
+ error={errors['address.city']?.[0]}
495
+ />
496
+ <Input
497
+ label="State / Province"
498
+ placeholder="State or Province"
499
+ value={quickAddState}
500
+ onChange={(e) => setQuickAddState(e.target.value)}
501
+ error={errors['address.state']?.[0]}
502
+ />
503
+ </div>
504
+ <div className="grid grid-cols-2 gap-3">
505
+ <Input
506
+ label="Postal Code"
507
+ placeholder="ZIP or Postal Code"
508
+ value={quickAddPostalCode}
509
+ onChange={(e) => setQuickAddPostalCode(e.target.value)}
510
+ error={errors['address.postal_code']?.[0]}
511
+ />
512
+ <Input
513
+ label="Country"
514
+ placeholder="Country"
515
+ value={quickAddCountry}
516
+ onChange={(e) => setQuickAddCountry(e.target.value)}
517
+ error={errors['address.country']?.[0]}
518
+ />
519
+ </div>
520
+ </div>
521
+ )}
522
+ </div>
523
+ )}
524
+ <div className="grid grid-cols-2 gap-3">
344
525
  {(partyType === "all" || partyType === "customer" || partyType === "supplier") && (
345
526
  <AsyncSearchableSelect
346
527
  label="Default Currency"