@hed-hog/contact 0.0.333 → 0.0.347

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.
@@ -63,30 +63,30 @@ type ImportResult = {
63
63
 
64
64
  type ColumnMapping = Record<string, string>;
65
65
 
66
- type CompanyOption = {
67
- id: number;
68
- name: string;
69
- trade_name?: string | null;
70
- };
71
-
72
- type WizardStep = 'upload' | 'preview' | 'mapping' | 'confirm' | 'result';
73
-
74
- function getImportErrorMessage(error: unknown, fallback: string) {
75
- if (typeof error === 'object' && error !== null) {
76
- const response = 'response' in error ? error.response : undefined;
77
- if (typeof response === 'object' && response !== null && 'data' in response) {
78
- const data = response.data;
79
- if (typeof data === 'object' && data !== null && 'message' in data) {
80
- const message = data.message;
81
- if (typeof message === 'string') return message;
82
- }
83
- }
84
- if ('message' in error && typeof error.message === 'string') {
85
- return error.message;
86
- }
87
- }
88
- return fallback;
89
- }
66
+ type CompanyOption = {
67
+ id: number;
68
+ name: string;
69
+ trade_name?: string | null;
70
+ };
71
+
72
+ type WizardStep = 'upload' | 'preview' | 'mapping' | 'confirm' | 'result';
73
+
74
+ function getImportErrorMessage(error: unknown, fallback: string) {
75
+ if (typeof error === 'object' && error !== null) {
76
+ const response = 'response' in error ? error.response : undefined;
77
+ if (typeof response === 'object' && response !== null && 'data' in response) {
78
+ const data = response.data;
79
+ if (typeof data === 'object' && data !== null && 'message' in data) {
80
+ const message = data.message;
81
+ if (typeof message === 'string') return message;
82
+ }
83
+ }
84
+ if ('message' in error && typeof error.message === 'string') {
85
+ return error.message;
86
+ }
87
+ }
88
+ return fallback;
89
+ }
90
90
 
91
91
  // ─── CRM Field Definitions ───────────────────────────────────────────────────
92
92
 
@@ -164,7 +164,7 @@ function StepIndicator({ current }: { current: WizardStep }) {
164
164
  <Icon className="size-3.5" />
165
165
  )}
166
166
  <span className="text-[10px] font-medium leading-tight hidden sm:block">
167
- {t(step.labelKey as never)}
167
+ {t(step.labelKey as never)}
168
168
  </span>
169
169
  </div>
170
170
  );
@@ -436,7 +436,7 @@ function MappingStep({
436
436
  {duplicateFields
437
437
  .map((field) => {
438
438
  const fieldDef = CRM_FIELDS.find((f) => f.value === field);
439
- const label = fieldDef ? t(fieldDef.labelKey as never) : field;
439
+ const label = fieldDef ? t(fieldDef.labelKey as never) : field;
440
440
  return t('importMappingDuplicateWarning', { field: label });
441
441
  })
442
442
  .join(' ')}
@@ -498,7 +498,7 @@ function MappingStep({
498
498
  value={field.value}
499
499
  className="text-xs"
500
500
  >
501
- {t(field.labelKey as never)}
501
+ {t(field.labelKey as never)}
502
502
  </SelectItem>
503
503
  ))}
504
504
  </SelectContent>
@@ -581,7 +581,7 @@ function ConfirmStep({
581
581
  variant="outline"
582
582
  className="text-[10px] border-primary/30 bg-primary/5 text-primary px-1.5 py-0"
583
583
  >
584
- {fieldDef ? t(fieldDef.labelKey as never) : field}
584
+ {fieldDef ? t(fieldDef.labelKey as never) : field}
585
585
  </Badge>
586
586
  </div>
587
587
  );
@@ -605,11 +605,11 @@ function ConfirmStep({
605
605
  onChange={(val) => {
606
606
  onCompanyChange(val ? Number(val) : null);
607
607
  }}
608
- getOptionValue={(opt) => (opt as CompanyOption).id}
609
- getOptionLabel={(opt) => (opt as CompanyOption).name ?? ''}
610
- getOptionDescription={(opt) =>
611
- (opt as CompanyOption).trade_name ?? undefined
612
- }
608
+ getOptionValue={(opt) => (opt as CompanyOption).id}
609
+ getOptionLabel={(opt) => (opt as CompanyOption).name ?? ''}
610
+ getOptionDescription={(opt) =>
611
+ (opt as CompanyOption).trade_name ?? undefined
612
+ }
613
613
  loadOptions={async ({ page, pageSize, search }) => {
614
614
  const params = new URLSearchParams({
615
615
  page: String(page),
@@ -816,29 +816,29 @@ function ResultStep({
816
816
 
817
817
  // ─── Main Sheet ──────────────────────────────────────────────────────────────
818
818
 
819
- export type PersonImportSheetProps = {
820
- open: boolean;
821
- onOpenChange: (open: boolean) => void;
822
- onSuccess: (result?: ImportResult) => void;
823
- initialCompanyId?: number | null;
824
- previewUrl?: string;
825
- importUrl?: string;
826
- title?: string;
827
- description?: string;
828
- nameRequired?: boolean;
829
- };
830
-
831
- export function PersonImportSheet({
832
- open,
833
- onOpenChange,
834
- onSuccess,
835
- initialCompanyId = null,
836
- previewUrl = '/person/import/preview',
837
- importUrl = '/person/import',
838
- title,
839
- description,
840
- nameRequired = true,
841
- }: PersonImportSheetProps) {
819
+ export type PersonImportSheetProps = {
820
+ open: boolean;
821
+ onOpenChange: (open: boolean) => void;
822
+ onSuccess: (result?: ImportResult) => void;
823
+ initialCompanyId?: number | null;
824
+ previewUrl?: string;
825
+ importUrl?: string;
826
+ title?: string;
827
+ description?: string;
828
+ nameRequired?: boolean;
829
+ };
830
+
831
+ export function PersonImportSheet({
832
+ open,
833
+ onOpenChange,
834
+ onSuccess,
835
+ initialCompanyId = null,
836
+ previewUrl = '/person/import/preview',
837
+ importUrl = '/person/import',
838
+ title,
839
+ description,
840
+ nameRequired = true,
841
+ }: PersonImportSheetProps) {
842
842
  const t = useTranslations('contact.ContactPage');
843
843
  const { request } = useApp();
844
844
 
@@ -886,8 +886,8 @@ export function PersonImportSheet({
886
886
  }
887
887
  onOpenChange(nextOpen);
888
888
  },
889
- [initialCompanyId, onOpenChange]
890
- );
889
+ [initialCompanyId, onOpenChange]
890
+ );
891
891
 
892
892
  // ── Auto-initialise mapping from columns ──
893
893
  const initMapping = useCallback((columns: string[]) => {
@@ -899,16 +899,16 @@ export function PersonImportSheet({
899
899
  }, []);
900
900
 
901
901
  // ── Navigation ──
902
- const canGoNext = (): boolean => {
903
- if (step === 'upload') return !!file;
904
- if (step === 'preview') return !!preview && !previewError;
905
- if (step === 'mapping') {
906
- const hasName = Object.values(mapping).includes('name');
907
- const hasEmail = Object.values(mapping).includes('email');
908
- return nameRequired ? hasName : hasEmail;
909
- }
910
- if (step === 'confirm') return true;
911
- return false;
902
+ const canGoNext = (): boolean => {
903
+ if (step === 'upload') return !!file;
904
+ if (step === 'preview') return !!preview && !previewError;
905
+ if (step === 'mapping') {
906
+ const hasName = Object.values(mapping).includes('name');
907
+ const hasEmail = Object.values(mapping).includes('email');
908
+ return nameRequired ? hasName : hasEmail;
909
+ }
910
+ if (step === 'confirm') return true;
911
+ return false;
912
912
  };
913
913
 
914
914
  const handleNext = async () => {
@@ -926,19 +926,19 @@ export function PersonImportSheet({
926
926
  await fetchPreview();
927
927
  } else if (step === 'preview') {
928
928
  setStep('mapping');
929
- } else if (step === 'mapping') {
930
- const hasName = Object.values(mapping).includes('name');
931
- const hasEmail = Object.values(mapping).includes('email');
932
- if (nameRequired && !hasName) {
933
- setMappingError(t('importMappingNameRequired'));
934
- return;
935
- }
936
- if (!nameRequired && !hasEmail) {
937
- setMappingError('Mapeie uma coluna para Email.');
938
- return;
939
- }
940
- setMappingError(null);
941
- setStep('confirm');
929
+ } else if (step === 'mapping') {
930
+ const hasName = Object.values(mapping).includes('name');
931
+ const hasEmail = Object.values(mapping).includes('email');
932
+ if (nameRequired && !hasName) {
933
+ setMappingError(t('importMappingNameRequired'));
934
+ return;
935
+ }
936
+ if (!nameRequired && !hasEmail) {
937
+ setMappingError('Mapeie uma coluna para Email.');
938
+ return;
939
+ }
940
+ setMappingError(null);
941
+ setStep('confirm');
942
942
  } else if (step === 'confirm') {
943
943
  await runImport();
944
944
  }
@@ -961,17 +961,17 @@ export function PersonImportSheet({
961
961
  const formData = new FormData();
962
962
  formData.append('file', file);
963
963
 
964
- const res = await request<ImportPreview>({
965
- url: previewUrl,
966
- method: 'POST',
967
- data: formData,
964
+ const res = await request<ImportPreview>({
965
+ url: previewUrl,
966
+ method: 'POST',
967
+ data: formData,
968
968
  headers: { 'Content-Type': 'multipart/form-data' },
969
969
  });
970
970
 
971
971
  setPreview(res.data);
972
972
  initMapping(res.data.columns);
973
- } catch (err: unknown) {
974
- setPreviewError(getImportErrorMessage(err, t('importErrorGeneric')));
973
+ } catch (err: unknown) {
974
+ setPreviewError(getImportErrorMessage(err, t('importErrorGeneric')));
975
975
  } finally {
976
976
  setPreviewLoading(false);
977
977
  }
@@ -990,17 +990,17 @@ export function PersonImportSheet({
990
990
  formData.append('mapping', JSON.stringify(mapping));
991
991
  if (companyId) formData.append('company_id', String(companyId));
992
992
 
993
- const res = await request<ImportResult>({
994
- url: importUrl,
995
- method: 'POST',
996
- data: formData,
993
+ const res = await request<ImportResult>({
994
+ url: importUrl,
995
+ method: 'POST',
996
+ data: formData,
997
997
  headers: { 'Content-Type': 'multipart/form-data' },
998
998
  });
999
-
1000
- setResult(res.data);
1001
- onSuccess(res.data);
1002
- } catch (err: unknown) {
1003
- setImportError(getImportErrorMessage(err, t('importErrorGeneric')));
999
+
1000
+ setResult(res.data);
1001
+ onSuccess(res.data);
1002
+ } catch (err: unknown) {
1003
+ setImportError(getImportErrorMessage(err, t('importErrorGeneric')));
1004
1004
  } finally {
1005
1005
  setImportLoading(false);
1006
1006
  }
@@ -1019,12 +1019,12 @@ export function PersonImportSheet({
1019
1019
  <Upload className="h-4 w-4 text-primary" />
1020
1020
  </div>
1021
1021
  <div>
1022
- <SheetTitle className="text-base">
1023
- {title ?? t('importSheetTitle')}
1024
- </SheetTitle>
1025
- <SheetDescription className="text-xs">
1026
- {description ?? t('importSheetDescription')}
1027
- </SheetDescription>
1022
+ <SheetTitle className="text-base">
1023
+ {title ?? t('importSheetTitle')}
1024
+ </SheetTitle>
1025
+ <SheetDescription className="text-xs">
1026
+ {description ?? t('importSheetDescription')}
1027
+ </SheetDescription>
1028
1028
  </div>
1029
1029
  </div>
1030
1030
  </SheetHeader>
@@ -33,6 +33,7 @@ import {
33
33
  import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
34
34
  import { formatDate } from '@/lib/format-date';
35
35
  import { cn } from '@/lib/utils';
36
+ import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
36
37
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
37
38
  import {
38
39
  type ColumnDef,
@@ -328,7 +329,11 @@ export default function PeoplePage() {
328
329
 
329
330
  const [sorting, setSorting] = useState<SortingState>([]);
330
331
  const [page, setPage] = useState(1);
331
- const [pageSize, setPageSize] = useState(12);
332
+ const [pageSize, setPageSize] = usePersistedPageSize({
333
+ storageKey: 'pagination:global:pageSize',
334
+ defaultValue: 12,
335
+ allowedValues: [12, 20, 30, 40, 50],
336
+ });
332
337
  const [searchInput, setSearchInput] = useState('');
333
338
  const [debouncedSearch, setDebouncedSearch] = useState('');
334
339
  const [typeFilter, setTypeFilter] = useState('all');