@hed-hog/contact 0.0.328 → 0.0.329

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.
@@ -44,7 +44,13 @@ import { zodResolver } from '@hookform/resolvers/zod';
44
44
  import { ChevronsUpDown, Loader2, Plus, X } from 'lucide-react';
45
45
  import { useTranslations } from 'next-intl';
46
46
  import { useEffect, useMemo, useRef, useState } from 'react';
47
- import { useForm } from 'react-hook-form';
47
+ import {
48
+ type FieldValues,
49
+ type Path,
50
+ type UseFormReturn,
51
+ useForm,
52
+ useWatch,
53
+ } from 'react-hook-form';
48
54
  import { z } from 'zod';
49
55
 
50
56
  type PersonOption = {
@@ -66,6 +72,7 @@ type CreatePersonValues = {
66
72
  };
67
73
 
68
74
  type PersonTypeFilter = 'individual' | 'company' | 'all';
75
+ type FieldValueType = 'string' | 'number';
69
76
 
70
77
  type ContactTypeLookup = {
71
78
  code: string;
@@ -88,7 +95,7 @@ type PaginatedResponse<T> = {
88
95
  data?: T[];
89
96
  };
90
97
 
91
- function usePersonFieldWithCreateTranslations() {
98
+ function usePersonPickerTranslations() {
92
99
  const tRoot = useTranslations();
93
100
 
94
101
  return (
@@ -138,7 +145,7 @@ function CreatePersonSheet({
138
145
  }) {
139
146
  const { request, showToastHandler, currentLocaleCode, getSettingValue } =
140
147
  useApp();
141
- const t = usePersonFieldWithCreateTranslations();
148
+ const t = usePersonPickerTranslations();
142
149
  const allowCompanyRegistration =
143
150
  getSettingValue('contact-allow-company-registration') !== false;
144
151
 
@@ -575,7 +582,7 @@ export function PersonPicker({
575
582
  disabled?: boolean;
576
583
  }) {
577
584
  const { request } = useApp();
578
- const t = usePersonFieldWithCreateTranslations();
585
+ const t = usePersonPickerTranslations();
579
586
  const [personOpen, setPersonOpen] = useState(false);
580
587
  const [personSearch, setPersonSearch] = useState('');
581
588
  const [debouncedPersonSearch, setDebouncedPersonSearch] = useState('');
@@ -948,3 +955,70 @@ export function PersonPicker({
948
955
  </>
949
956
  );
950
957
  }
958
+
959
+ function parsePersonFieldValue(
960
+ personId: number | null,
961
+ valueType: FieldValueType
962
+ ) {
963
+ if (personId === null) {
964
+ return valueType === 'number' ? null : '';
965
+ }
966
+
967
+ return valueType === 'number' ? personId : String(personId);
968
+ }
969
+
970
+ export function PersonPickerField<TFieldValues extends FieldValues>({
971
+ form,
972
+ name,
973
+ label,
974
+ entityLabel,
975
+ selectPlaceholder,
976
+ personTypeFilter = 'all',
977
+ createType = 'individual',
978
+ lockCreateType = false,
979
+ valueType = 'string',
980
+ initialSelectedLabel = '',
981
+ disabled = false,
982
+ }: {
983
+ form: UseFormReturn<TFieldValues>;
984
+ name: Path<TFieldValues>;
985
+ label: string;
986
+ entityLabel: string;
987
+ selectPlaceholder: string;
988
+ personTypeFilter?: PersonTypeFilter;
989
+ createType?: Exclude<PersonTypeFilter, 'all'>;
990
+ lockCreateType?: boolean;
991
+ valueType?: FieldValueType;
992
+ initialSelectedLabel?: string;
993
+ disabled?: boolean;
994
+ }) {
995
+ const fieldValue = useWatch({
996
+ control: form.control,
997
+ name,
998
+ }) as string | number | null | undefined;
999
+
1000
+ return (
1001
+ <PersonPicker
1002
+ label={label}
1003
+ entityLabel={entityLabel}
1004
+ value={fieldValue ?? null}
1005
+ onChange={(personId) => {
1006
+ form.setValue(
1007
+ name,
1008
+ parsePersonFieldValue(personId, valueType) as never,
1009
+ {
1010
+ shouldValidate: false,
1011
+ shouldDirty: true,
1012
+ shouldTouch: true,
1013
+ }
1014
+ );
1015
+ }}
1016
+ selectPlaceholder={selectPlaceholder}
1017
+ personTypeFilter={personTypeFilter}
1018
+ createType={createType}
1019
+ lockCreateType={lockCreateType}
1020
+ initialSelectedLabel={initialSelectedLabel}
1021
+ disabled={disabled}
1022
+ />
1023
+ );
1024
+ }
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { PersonFieldWithCreate } from '@/app/(app)/(libraries)/contact/person/_components/person-field-with-create';
3
+ import { PersonPickerField } from '@/app/(app)/(libraries)/contact/_components/person-picker';
4
4
  import {
5
5
  AlertDialog,
6
6
  AlertDialogContent,
@@ -2402,7 +2402,7 @@ export function PersonFormSheet({
2402
2402
 
2403
2403
  {allowCompanyRegistration ? (
2404
2404
  <div className="space-y-1.5">
2405
- <PersonFieldWithCreate<PersonFormValues>
2405
+ <PersonPickerField<PersonFormValues>
2406
2406
  form={form}
2407
2407
  name="employer_company_id"
2408
2408
  label={t('employerCompany')}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/contact",
3
- "version": "0.0.328",
3
+ "version": "0.0.329",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -9,13 +9,13 @@
9
9
  "@nestjs/core": "^11",
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
- "@hed-hog/api": "0.0.8",
13
12
  "@hed-hog/api-locale": "0.0.14",
14
13
  "@hed-hog/api-mail": "0.0.9",
15
- "@hed-hog/address": "0.0.328",
16
- "@hed-hog/api-prisma": "0.0.6",
17
- "@hed-hog/core": "0.0.328",
18
- "@hed-hog/api-pagination": "0.0.7"
14
+ "@hed-hog/api-pagination": "0.0.7",
15
+ "@hed-hog/core": "0.0.329",
16
+ "@hed-hog/api": "0.0.8",
17
+ "@hed-hog/address": "0.0.329",
18
+ "@hed-hog/api-prisma": "0.0.6"
19
19
  },
20
20
  "exports": {
21
21
  ".": {
@@ -1,110 +0,0 @@
1
- 'use client';
2
-
3
- import { CrmNav } from './crm-nav';
4
- import { Page, PageHeader } from '@/components/entity-list';
5
- import { Badge } from '@/components/ui/badge';
6
- import { Button } from '@/components/ui/button';
7
- import {
8
- Card,
9
- CardContent,
10
- CardDescription,
11
- CardHeader,
12
- CardTitle,
13
- } from '@/components/ui/card';
14
- import type { LucideIcon } from 'lucide-react';
15
- import { ArrowRight, Sparkles } from 'lucide-react';
16
- import Link from 'next/link';
17
- import { useTranslations } from 'next-intl';
18
-
19
- type CrmComingSoonProps = {
20
- currentHref: string;
21
- titleKey: string;
22
- descriptionKey: string;
23
- icon: LucideIcon;
24
- };
25
-
26
- export function CrmComingSoon({
27
- currentHref,
28
- titleKey,
29
- descriptionKey,
30
- icon: Icon,
31
- }: CrmComingSoonProps) {
32
- const t = useTranslations('contact.CrmMenu');
33
- const common = useTranslations('contact.CrmFuturePage');
34
-
35
- return (
36
- <Page>
37
- <PageHeader
38
- breadcrumbs={[
39
- { label: common('breadcrumbs.home'), href: '/' },
40
- { label: common('breadcrumbs.crm'), href: '/contact/dashboard' },
41
- { label: t(`sections.${titleKey}.title`) },
42
- ]}
43
- title={t(`sections.${titleKey}.title`)}
44
- description={t(`sections.${descriptionKey}.description`)}
45
- />
46
-
47
- <div className="min-w-0 space-y-6 overflow-x-hidden">
48
- <CrmNav currentHref={currentHref} />
49
-
50
- <Card className="overflow-hidden border-orange-200/70 bg-gradient-to-br from-orange-50 via-background to-amber-50 py-0">
51
- <CardContent className="grid gap-6 px-6 py-8 lg:grid-cols-[minmax(0,1.2fr)_minmax(260px,0.8fr)]">
52
- <div className="min-w-0 space-y-4">
53
- <Badge className="w-fit rounded-full bg-orange-500/10 px-3 py-1 text-orange-700 hover:bg-orange-500/10">
54
- <Sparkles className="mr-2 size-3.5" />
55
- {common('badge')}
56
- </Badge>
57
- <div className="space-y-3">
58
- <div className="flex items-center gap-3">
59
- <div className="rounded-2xl bg-orange-500/10 p-3 text-orange-700">
60
- <Icon className="size-5" />
61
- </div>
62
- <h2 className="text-3xl font-semibold tracking-tight">
63
- {t(`sections.${titleKey}.title`)}
64
- </h2>
65
- </div>
66
- <p className="max-w-2xl text-sm leading-6 text-muted-foreground">
67
- {common('description')}
68
- </p>
69
- </div>
70
- <div className="flex flex-wrap gap-3">
71
- <Button asChild>
72
- <Link href="/contact/dashboard">
73
- {common('backToDashboard')}
74
- </Link>
75
- </Button>
76
- <Button asChild variant="outline">
77
- <Link href="/contact/pipeline">{common('seePipeline')}</Link>
78
- </Button>
79
- </div>
80
- </div>
81
-
82
- <Card className="border-white/70 bg-white/80 shadow-sm">
83
- <CardHeader>
84
- <CardTitle>{common('nextStepTitle')}</CardTitle>
85
- <CardDescription>
86
- {common('nextStepDescription')}
87
- </CardDescription>
88
- </CardHeader>
89
- <CardContent className="space-y-3">
90
- {['stepOne', 'stepTwo', 'stepThree'].map((step) => (
91
- <div
92
- key={step}
93
- className="flex items-start gap-3 rounded-2xl border border-border/70 bg-background px-4 py-3"
94
- >
95
- <div className="mt-0.5 rounded-full bg-orange-500/10 p-1.5 text-orange-700">
96
- <ArrowRight className="size-3.5" />
97
- </div>
98
- <p className="text-sm text-muted-foreground">
99
- {common(step)}
100
- </p>
101
- </div>
102
- ))}
103
- </CardContent>
104
- </Card>
105
- </CardContent>
106
- </Card>
107
- </div>
108
- </Page>
109
- );
110
- }