@recruitnepal/shared-packages 1.7.1 → 1.7.2
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/dist/components/cv/ResponsivePreview.d.ts +7 -0
- package/dist/components/cv/ResponsivePreview.d.ts.map +1 -0
- package/dist/components/cv/ResponsivePreview.js +47 -0
- package/dist/components/cv-builder/forms/AboutForm.d.ts +2 -0
- package/dist/components/cv-builder/forms/AboutForm.d.ts.map +1 -0
- package/dist/components/cv-builder/forms/AboutForm.js +86 -0
- package/dist/components/cv-builder/forms/AdditionalDetailsForm.d.ts +2 -0
- package/dist/components/cv-builder/forms/AdditionalDetailsForm.d.ts.map +1 -0
- package/dist/components/cv-builder/forms/AdditionalDetailsForm.js +227 -0
- package/dist/components/cv-builder/forms/EducationForm.d.ts +2 -0
- package/dist/components/cv-builder/forms/EducationForm.d.ts.map +1 -0
- package/dist/components/cv-builder/forms/EducationForm.js +168 -0
- package/dist/components/cv-builder/forms/ExperienceForm.d.ts +2 -0
- package/dist/components/cv-builder/forms/ExperienceForm.d.ts.map +1 -0
- package/dist/components/cv-builder/forms/ExperienceForm.js +266 -0
- package/dist/components/cv-builder/forms/PortfolioForm.d.ts +2 -0
- package/dist/components/cv-builder/forms/PortfolioForm.d.ts.map +1 -0
- package/dist/components/cv-builder/forms/PortfolioForm.js +159 -0
- package/dist/components/cv-builder/forms/ProjectsForm.d.ts +2 -0
- package/dist/components/cv-builder/forms/ProjectsForm.d.ts.map +1 -0
- package/dist/components/cv-builder/forms/ProjectsForm.js +134 -0
- package/dist/components/cv-builder/forms/SkillsForm.d.ts +2 -0
- package/dist/components/cv-builder/forms/SkillsForm.d.ts.map +1 -0
- package/dist/components/cv-builder/forms/SkillsForm.js +250 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +50 -51
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CvTemplateRegistry } from './TemplateRenderer';
|
|
2
|
+
export type ResponsivePreviewProps = {
|
|
3
|
+
/** Template registry provided by the host app */
|
|
4
|
+
registry: CvTemplateRegistry;
|
|
5
|
+
};
|
|
6
|
+
export default function ResponsivePreview({ registry }: ResponsivePreviewProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=ResponsivePreview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ResponsivePreview.d.ts","sourceRoot":"","sources":["../../../src/components/cv/ResponsivePreview.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAM7D,MAAM,MAAM,sBAAsB,GAAG;IACnC,iDAAiD;IACjD,QAAQ,EAAE,kBAAkB,CAAC;CAC9B,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,EAAE,QAAQ,EAAE,EAAE,sBAAsB,2CAiE7E"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// components/cv/ResponsivePreview.tsx
|
|
2
|
+
'use client';
|
|
3
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
4
|
+
import { useEffect, useRef, useState } from 'react';
|
|
5
|
+
import TemplateRenderer from './TemplateRenderer';
|
|
6
|
+
import { useCvPreview } from '../../stores/cvPreview.store';
|
|
7
|
+
// If your paper is a different exact size, adjust these
|
|
8
|
+
const BASE_WIDTH = 794; // A4 @ ~96 dpi
|
|
9
|
+
const BASE_HEIGHT = 1123;
|
|
10
|
+
export default function ResponsivePreview({ registry }) {
|
|
11
|
+
const { slug, live } = useCvPreview();
|
|
12
|
+
const wrapRef = useRef(null);
|
|
13
|
+
const [scale, setScale] = useState(1);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!wrapRef.current)
|
|
16
|
+
return;
|
|
17
|
+
const el = wrapRef.current;
|
|
18
|
+
const measure = () => {
|
|
19
|
+
const rect = el.getBoundingClientRect();
|
|
20
|
+
// leave a little breathing room (2%)
|
|
21
|
+
const sW = rect.width / BASE_WIDTH;
|
|
22
|
+
const sH = rect.height / BASE_HEIGHT;
|
|
23
|
+
const s = Math.max(0.3, Math.min(1, Math.min(sW, sH) * 0.98));
|
|
24
|
+
setScale(s);
|
|
25
|
+
};
|
|
26
|
+
measure();
|
|
27
|
+
const ro = new ResizeObserver(measure);
|
|
28
|
+
ro.observe(el);
|
|
29
|
+
window.addEventListener('orientationchange', measure);
|
|
30
|
+
return () => {
|
|
31
|
+
ro.disconnect();
|
|
32
|
+
window.removeEventListener('orientationchange', measure);
|
|
33
|
+
};
|
|
34
|
+
}, []);
|
|
35
|
+
if (!live) {
|
|
36
|
+
return (_jsx("div", { className: "text-sm text-muted-foreground p-4", children: "Fill the forms to see the preview." }));
|
|
37
|
+
}
|
|
38
|
+
const scaledW = BASE_WIDTH * scale;
|
|
39
|
+
const scaledH = BASE_HEIGHT * scale;
|
|
40
|
+
return (
|
|
41
|
+
// This box gets as big as the dialog gives it, and can scroll both ways
|
|
42
|
+
_jsx("div", { ref: wrapRef, className: "h-full w-full overflow-auto", children: _jsx("div", { className: "w-full h-full flex items-start justify-center", children: _jsx("div", { className: "relative", style: { width: scaledW, height: scaledH }, children: _jsx("div", { className: "absolute left-0 top-0 origin-top-left", style: {
|
|
43
|
+
width: BASE_WIDTH,
|
|
44
|
+
height: BASE_HEIGHT,
|
|
45
|
+
transform: `scale(${scale})`,
|
|
46
|
+
}, children: _jsx("div", { id: "cv-root-mobile", className: "bg-white border rounded-xl shadow-sm", children: _jsx(TemplateRenderer, { registry: registry, slug: slug, data: live }) }) }) }) }) }));
|
|
47
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AboutForm.d.ts","sourceRoot":"","sources":["../../../../src/components/cv-builder/forms/AboutForm.tsx"],"names":[],"mappings":"AAkBA,MAAM,CAAC,OAAO,UAAU,SAAS,4CA2NhC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useCallback } from 'react';
|
|
4
|
+
import { useForm } from 'react-hook-form';
|
|
5
|
+
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form';
|
|
6
|
+
import { Input } from '@/components/ui/input';
|
|
7
|
+
import { Textarea } from '@/components/ui/textarea';
|
|
8
|
+
import { useCvPreview } from '@/stores/cvPreview.store';
|
|
9
|
+
import { useCvDraftsStore } from '@/stores/cvDrafts.store';
|
|
10
|
+
export default function AboutForm() {
|
|
11
|
+
const { live, setPersonal } = useCvPreview();
|
|
12
|
+
const { getCurrentDraft, updateDraft } = useCvDraftsStore();
|
|
13
|
+
// Initialize form with current draft data or empty
|
|
14
|
+
const personalInfo = live?.personalInfo || {
|
|
15
|
+
firstName: '',
|
|
16
|
+
lastName: '',
|
|
17
|
+
email: '',
|
|
18
|
+
phone: '',
|
|
19
|
+
location: { city: '', state: '', country: 'Nepal' },
|
|
20
|
+
summary: '',
|
|
21
|
+
links: [],
|
|
22
|
+
};
|
|
23
|
+
const form = useForm({
|
|
24
|
+
defaultValues: {
|
|
25
|
+
firstName: personalInfo?.firstName || '',
|
|
26
|
+
lastName: personalInfo?.lastName || '',
|
|
27
|
+
email: personalInfo?.email || '',
|
|
28
|
+
phone: personalInfo?.phone || '',
|
|
29
|
+
city: personalInfo?.location?.city || '',
|
|
30
|
+
state: personalInfo?.location?.state || '',
|
|
31
|
+
country: personalInfo?.location?.country || 'Nepal',
|
|
32
|
+
summary: personalInfo?.summary || '',
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
// Update form when draft changes
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (live?.personalInfo) {
|
|
38
|
+
form.reset({
|
|
39
|
+
firstName: live.personalInfo.firstName || '',
|
|
40
|
+
lastName: live.personalInfo.lastName || '',
|
|
41
|
+
email: live.personalInfo.email || '',
|
|
42
|
+
phone: live.personalInfo.phone || '',
|
|
43
|
+
city: live.personalInfo.location?.city || '',
|
|
44
|
+
state: live.personalInfo.location?.state || '',
|
|
45
|
+
country: live.personalInfo.location?.country || 'Nepal',
|
|
46
|
+
summary: live.personalInfo.summary || '',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}, [live?.personalInfo, form]);
|
|
50
|
+
const debounceRef = useRef(null);
|
|
51
|
+
const setPersonalDebounced = useCallback((payload) => {
|
|
52
|
+
clearTimeout(debounceRef.current);
|
|
53
|
+
debounceRef.current = setTimeout(() => {
|
|
54
|
+
setPersonal(payload);
|
|
55
|
+
// Also update the draft in store
|
|
56
|
+
const currentDraft = getCurrentDraft();
|
|
57
|
+
if (currentDraft && live) {
|
|
58
|
+
updateDraft(currentDraft.id, {
|
|
59
|
+
cv_data: {
|
|
60
|
+
...live,
|
|
61
|
+
personalInfo: { ...live.personalInfo, ...payload },
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}, 300);
|
|
66
|
+
}, [setPersonal, getCurrentDraft, updateDraft, live]);
|
|
67
|
+
// Watch form changes and update preview
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
const sub = form.watch((v) => {
|
|
70
|
+
setPersonalDebounced({
|
|
71
|
+
firstName: v.firstName ?? '',
|
|
72
|
+
lastName: v.lastName ?? '',
|
|
73
|
+
email: v.email ?? '',
|
|
74
|
+
phone: v.phone ?? '',
|
|
75
|
+
location: {
|
|
76
|
+
city: v.city ?? '',
|
|
77
|
+
state: v.state ?? '',
|
|
78
|
+
country: v.country ?? 'Nepal',
|
|
79
|
+
},
|
|
80
|
+
summary: v.summary ?? '',
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
return () => sub.unsubscribe();
|
|
84
|
+
}, [form, setPersonalDebounced]);
|
|
85
|
+
return (_jsxs("div", { className: "mb-6", children: [_jsx("h1", { className: "text-2xl mb-2 font-medium", children: "About Yourself" }), _jsx("p", { className: "text-sm mb-6", children: "Fill in your personal information to build your CV. This data is saved to your CV draft." }), _jsx(Form, { ...form, children: _jsxs("form", { className: "space-y-6", onSubmit: (e) => e.preventDefault(), children: [_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(FormField, { control: form.control, name: "firstName", render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "First Name *" }), _jsx(FormControl, { children: _jsx(Input, { placeholder: "John", ...field }) }), _jsx(FormMessage, {})] })) }), _jsx(FormField, { control: form.control, name: "lastName", render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "Last Name *" }), _jsx(FormControl, { children: _jsx(Input, { placeholder: "Doe", ...field }) }), _jsx(FormMessage, {})] })) })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(FormField, { control: form.control, name: "email", render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "Email *" }), _jsx(FormControl, { children: _jsx(Input, { type: "email", placeholder: "john@example.com", ...field }) }), _jsx(FormMessage, {})] })) }), _jsx(FormField, { control: form.control, name: "phone", render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "Phone" }), _jsx(FormControl, { children: _jsx(Input, { placeholder: "98XXXXXXXX", ...field }) }), _jsx(FormMessage, {})] })) })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4", children: [_jsx(FormField, { control: form.control, name: "city", render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "City" }), _jsx(FormControl, { children: _jsx(Input, { placeholder: "Kathmandu", ...field }) }), _jsx(FormMessage, {})] })) }), _jsx(FormField, { control: form.control, name: "state", render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "State/Province" }), _jsx(FormControl, { children: _jsx(Input, { placeholder: "Bagmati", ...field }) }), _jsx(FormMessage, {})] })) }), _jsx(FormField, { control: form.control, name: "country", render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "Country" }), _jsx(FormControl, { children: _jsx(Input, { placeholder: "Nepal", ...field }) }), _jsx(FormMessage, {})] })) })] }), _jsx(FormField, { control: form.control, name: "summary", render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "Professional Summary" }), _jsx(FormControl, { children: _jsx(Textarea, { placeholder: "Write a brief summary about yourself...", className: "min-h-[120px]", ...field }) }), _jsx(FormMessage, {})] })) })] }) })] }));
|
|
86
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AdditionalDetailsForm.d.ts","sourceRoot":"","sources":["../../../../src/components/cv-builder/forms/AdditionalDetailsForm.tsx"],"names":[],"mappings":"AAoEA,MAAM,CAAC,OAAO,UAAU,qBAAqB,4CA4f5C"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useCallback, useMemo, useState } from 'react';
|
|
4
|
+
import { useForm, useFieldArray } from 'react-hook-form';
|
|
5
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form';
|
|
8
|
+
import { Input } from '@/components/ui/input';
|
|
9
|
+
import { Button } from '@/components/ui/button';
|
|
10
|
+
import { useCvPreview } from '@/stores/cvPreview.store';
|
|
11
|
+
import { useCvDraftsStore } from '@/stores/cvDrafts.store';
|
|
12
|
+
import { Plus, Trash2 } from 'lucide-react';
|
|
13
|
+
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from '@/components/ui/accordion';
|
|
14
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select';
|
|
15
|
+
import { RatingStars } from '@/components/RatingStars';
|
|
16
|
+
import NoData from '@/components/common/no-data';
|
|
17
|
+
const languageSchema = z.object({
|
|
18
|
+
id: z.string().optional(),
|
|
19
|
+
language: z.string().min(1, { message: 'Language is required' }),
|
|
20
|
+
reading: z.number().int().min(0).max(5),
|
|
21
|
+
writing: z.number().int().min(0).max(5),
|
|
22
|
+
speaking: z.number().int().min(0).max(5),
|
|
23
|
+
});
|
|
24
|
+
const languageFormSchema = z.object({
|
|
25
|
+
languageEntries: z.array(languageSchema).min(0),
|
|
26
|
+
});
|
|
27
|
+
const referenceSchema = z.object({
|
|
28
|
+
id: z.string().optional(),
|
|
29
|
+
reference_person: z
|
|
30
|
+
.string()
|
|
31
|
+
.min(1, { message: 'Reference Person is required' }),
|
|
32
|
+
position: z.string().min(1, { message: 'Position is required' }),
|
|
33
|
+
email: z.string().email().min(1, { message: 'Email is required' }),
|
|
34
|
+
phone_number: z
|
|
35
|
+
.string()
|
|
36
|
+
.min(1, { message: 'Phone number is required' })
|
|
37
|
+
.max(10, { message: 'Invalid Phone number' }),
|
|
38
|
+
});
|
|
39
|
+
const referenceFormSchema = z.object({
|
|
40
|
+
referenceEntries: z.array(referenceSchema).min(0),
|
|
41
|
+
});
|
|
42
|
+
export default function AdditionalDetailsForm() {
|
|
43
|
+
const { live, setSections } = useCvPreview();
|
|
44
|
+
const { getCurrentDraft, updateDraft } = useCvDraftsStore();
|
|
45
|
+
// Control accordion state to prevent auto-closing
|
|
46
|
+
const [openLanguageAccordion, setOpenLanguageAccordion] = useState(undefined);
|
|
47
|
+
const [openReferenceAccordion, setOpenReferenceAccordion] = useState(undefined);
|
|
48
|
+
// Transform languages from live data to form format
|
|
49
|
+
const languagesList = useMemo(() => {
|
|
50
|
+
if (!live?.sections?.languages || !Array.isArray(live.sections.languages)) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
return live.sections.languages.map((lang) => ({
|
|
54
|
+
id: lang.id || `lang_${Date.now()}`,
|
|
55
|
+
language: lang.language || '',
|
|
56
|
+
reading: typeof lang.reading === 'number' ? lang.reading : (lang.reading === 'Excellent' ? 5 : lang.reading === 'good' ? 3 : 1),
|
|
57
|
+
writing: typeof lang.writing === 'number' ? lang.writing : (lang.writing === 'Excellent' ? 5 : lang.writing === 'good' ? 3 : 1),
|
|
58
|
+
speaking: typeof lang.speaking === 'number' ? lang.speaking : (lang.speaking === 'Excellent' ? 5 : lang.speaking === 'good' ? 3 : 1),
|
|
59
|
+
}));
|
|
60
|
+
}, [live?.sections?.languages]);
|
|
61
|
+
// Transform references from live data to form format
|
|
62
|
+
const referencesList = useMemo(() => {
|
|
63
|
+
if (!live?.sections?.references || !Array.isArray(live.sections.references)) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
return live.sections.references.map((ref) => ({
|
|
67
|
+
id: ref.id || `ref_${Date.now()}`,
|
|
68
|
+
reference_person: ref.name || '',
|
|
69
|
+
position: ref.position || '',
|
|
70
|
+
email: ref.email || '',
|
|
71
|
+
phone_number: ref.phone || '',
|
|
72
|
+
}));
|
|
73
|
+
}, [live?.sections?.references]);
|
|
74
|
+
const languagesForm = useForm({
|
|
75
|
+
resolver: zodResolver(languageFormSchema),
|
|
76
|
+
defaultValues: {
|
|
77
|
+
languageEntries: languagesList.length > 0 ? languagesList : [],
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
const referencesForm = useForm({
|
|
81
|
+
resolver: zodResolver(referenceFormSchema),
|
|
82
|
+
defaultValues: {
|
|
83
|
+
referenceEntries: referencesList.length > 0 ? referencesList : [],
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
const { fields: langFields, append: appendLang, remove: removeLang } = useFieldArray({
|
|
87
|
+
control: languagesForm.control,
|
|
88
|
+
name: 'languageEntries',
|
|
89
|
+
});
|
|
90
|
+
const { fields: refFields, append: appendRef, remove: removeRef } = useFieldArray({
|
|
91
|
+
control: referencesForm.control,
|
|
92
|
+
name: 'referenceEntries',
|
|
93
|
+
});
|
|
94
|
+
// Track if we've initialized to prevent reset loops
|
|
95
|
+
const langInitializedRef = useRef(false);
|
|
96
|
+
const refInitializedRef = useRef(false);
|
|
97
|
+
const prevLanguagesRef = useRef('');
|
|
98
|
+
const prevReferencesRef = useRef('');
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
const currentListStr = JSON.stringify(languagesList);
|
|
101
|
+
if (!langInitializedRef.current && languagesList.length > 0) {
|
|
102
|
+
languagesForm.reset({ languageEntries: languagesList });
|
|
103
|
+
langInitializedRef.current = true;
|
|
104
|
+
prevLanguagesRef.current = currentListStr;
|
|
105
|
+
}
|
|
106
|
+
else if (langInitializedRef.current && prevLanguagesRef.current !== currentListStr) {
|
|
107
|
+
const prevList = JSON.parse(prevLanguagesRef.current || '[]');
|
|
108
|
+
const currentIds = languagesList.map(e => e.id).sort().join(',');
|
|
109
|
+
const prevIds = prevList.map((e) => e.id).sort().join(',');
|
|
110
|
+
if (currentIds !== prevIds || Math.abs(languagesList.length - prevList.length) > 0) {
|
|
111
|
+
languagesForm.reset({ languageEntries: languagesList });
|
|
112
|
+
prevLanguagesRef.current = currentListStr;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}, [languagesList, languagesForm]);
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
const currentListStr = JSON.stringify(referencesList);
|
|
118
|
+
if (!refInitializedRef.current && referencesList.length > 0) {
|
|
119
|
+
referencesForm.reset({ referenceEntries: referencesList });
|
|
120
|
+
refInitializedRef.current = true;
|
|
121
|
+
prevReferencesRef.current = currentListStr;
|
|
122
|
+
}
|
|
123
|
+
else if (refInitializedRef.current && prevReferencesRef.current !== currentListStr) {
|
|
124
|
+
const prevList = JSON.parse(prevReferencesRef.current || '[]');
|
|
125
|
+
const currentIds = referencesList.map(e => e.id).sort().join(',');
|
|
126
|
+
const prevIds = prevList.map((e) => e.id).sort().join(',');
|
|
127
|
+
if (currentIds !== prevIds || Math.abs(referencesList.length - prevList.length) > 0) {
|
|
128
|
+
referencesForm.reset({ referenceEntries: referencesList });
|
|
129
|
+
prevReferencesRef.current = currentListStr;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}, [referencesList, referencesForm]);
|
|
133
|
+
// ---- LIVE PREVIEW: Languages ----
|
|
134
|
+
const debRefLang = useRef(null);
|
|
135
|
+
const pushLanguages = useCallback(() => {
|
|
136
|
+
if (debRefLang.current)
|
|
137
|
+
window.clearTimeout(debRefLang.current);
|
|
138
|
+
debRefLang.current = window.setTimeout(() => {
|
|
139
|
+
const entries = languagesForm.getValues().languageEntries || [];
|
|
140
|
+
const languages = entries
|
|
141
|
+
.map((e, i) => ({
|
|
142
|
+
id: String(e.id ?? i),
|
|
143
|
+
language: e.language ?? '',
|
|
144
|
+
proficiency: undefined,
|
|
145
|
+
reading: e.reading || undefined,
|
|
146
|
+
writing: e.writing || undefined,
|
|
147
|
+
speaking: e.speaking || undefined,
|
|
148
|
+
}))
|
|
149
|
+
.filter((l) => l.language);
|
|
150
|
+
setSections({ languages });
|
|
151
|
+
}, 150);
|
|
152
|
+
}, [languagesForm, setSections]);
|
|
153
|
+
// ---- LIVE PREVIEW: References ----
|
|
154
|
+
const debRefRef = useRef(null);
|
|
155
|
+
const pushReferences = useCallback(() => {
|
|
156
|
+
if (debRefRef.current)
|
|
157
|
+
window.clearTimeout(debRefRef.current);
|
|
158
|
+
debRefRef.current = window.setTimeout(() => {
|
|
159
|
+
const entries = referencesForm.getValues().referenceEntries || [];
|
|
160
|
+
const references = entries
|
|
161
|
+
.map((e, i) => ({
|
|
162
|
+
id: String(e.id ?? i),
|
|
163
|
+
name: e.reference_person ?? '',
|
|
164
|
+
position: e.position || undefined,
|
|
165
|
+
email: e.email || undefined,
|
|
166
|
+
phone: e.phone_number || undefined,
|
|
167
|
+
company: undefined,
|
|
168
|
+
}))
|
|
169
|
+
.filter((r) => r.name);
|
|
170
|
+
setSections({ references });
|
|
171
|
+
}, 150);
|
|
172
|
+
}, [referencesForm, setSections]);
|
|
173
|
+
// push initial + on any change
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
pushLanguages();
|
|
176
|
+
}, [langFields.length, pushLanguages]);
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
const sub = languagesForm.watch(() => pushLanguages());
|
|
179
|
+
return () => sub.unsubscribe();
|
|
180
|
+
}, [languagesForm, pushLanguages]);
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
pushReferences();
|
|
183
|
+
}, [refFields.length, pushReferences]);
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
const sub = referencesForm.watch(() => pushReferences());
|
|
186
|
+
return () => sub.unsubscribe();
|
|
187
|
+
}, [referencesForm, pushReferences]);
|
|
188
|
+
// Update draft when data changes
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
const currentDraft = getCurrentDraft();
|
|
191
|
+
if (currentDraft && live) {
|
|
192
|
+
updateDraft(currentDraft.id, {
|
|
193
|
+
cv_data: live,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}, [live, getCurrentDraft, updateDraft]);
|
|
197
|
+
return (_jsxs("div", { className: "mb-6 space-y-8", children: [_jsx("h1", { className: "text-2xl mb-2 font-medium", children: "Additional Details" }), _jsx("p", { className: "text-sm mb-6", children: "Add languages and references to complete your CV." }), _jsxs("div", { children: [_jsx("h2", { className: "text-lg font-semibold mb-4", children: "Languages" }), _jsx(Form, { ...languagesForm, children: _jsxs("form", { className: "space-y-6", children: [langFields.length === 0 && _jsx(NoData, {}), langFields.map((field, index) => (_jsx(Accordion, { type: "single", collapsible: true, value: openLanguageAccordion, onValueChange: setOpenLanguageAccordion, children: _jsxs(AccordionItem, { value: `languages-${index}`, className: "border-none", children: [_jsxs("div", { className: "flex items-center justify-between bg-primary/10 py-3 px-4 rounded-xl accordion-grow", children: [_jsx(AccordionTrigger, { className: "h-[30px] flex-grow hover:no-underline p-0", children: _jsx("div", { className: "flex items-center justify-between w-full", children: _jsx("div", { className: "flex flex-col items-start", children: _jsx("p", { className: "text-base font-semibold", children: (field.language || 'New Language Entry')
|
|
198
|
+
.charAt(0)
|
|
199
|
+
.toUpperCase() +
|
|
200
|
+
(field.language || 'New Language Entry').slice(1) }) }) }) }), _jsx("div", { className: "pl-3 border-l border-black/60 ml-4", children: _jsx(Button, { variant: "link", type: "button", size: "sm", onClick: (e) => {
|
|
201
|
+
e.stopPropagation();
|
|
202
|
+
removeLang(index);
|
|
203
|
+
}, className: "h-8 w-8 p-0 hover:text-destructive", children: _jsx(Trash2, { size: 20, className: "text-text-primary hover:text-destructive" }) }) })] }), _jsx(AccordionContent, { children: _jsx("div", { className: "p-4 pt-0", children: _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(FormField, { control: languagesForm.control, name: `languageEntries.${index}.language`, render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "Language" }), _jsxs(Select, { onValueChange: (value) => {
|
|
204
|
+
field.onChange(value);
|
|
205
|
+
// Keep accordion open after selection
|
|
206
|
+
setOpenLanguageAccordion(`languages-${index}`);
|
|
207
|
+
}, defaultValue: field.value, value: field.value, children: [_jsx(FormControl, { children: _jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Select Language" }) }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "English", children: "English" }), _jsx(SelectItem, { value: "Nepali", children: "Nepali" }), _jsx(SelectItem, { value: "French", children: "French" }), _jsx(SelectItem, { value: "Spanish", children: "Spanish" }), _jsx(SelectItem, { value: "Hindi", children: "Hindi" }), _jsx(SelectItem, { value: "Mandarin", children: "Mandarin" }), _jsx(SelectItem, { value: "Korean", children: "Korean" }), _jsx(SelectItem, { value: "German", children: "German" })] })] }), _jsx(FormMessage, {})] })) }), _jsx(FormField, { control: languagesForm.control, name: `languageEntries.${index}.reading`, render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "Reading" }), _jsx(FormControl, { children: _jsx("div", { className: "text-yellow-500", children: _jsx(RatingStars, { "aria-label": "Reading", value: Number(field.value) || 0, onChange: (n) => field.onChange(n) }) }) }), _jsx(FormMessage, {})] })) }), _jsx(FormField, { control: languagesForm.control, name: `languageEntries.${index}.writing`, render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "Writing" }), _jsx(FormControl, { children: _jsx("div", { className: "text-yellow-500", children: _jsx(RatingStars, { "aria-label": "Writing", value: Number(field.value) || 0, onChange: (n) => field.onChange(n) }) }) }), _jsx(FormMessage, {})] })) }), _jsx(FormField, { control: languagesForm.control, name: `languageEntries.${index}.speaking`, render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "Speaking" }), _jsx(FormControl, { children: _jsx("div", { className: "text-yellow-500", children: _jsx(RatingStars, { "aria-label": "Speaking", value: Number(field.value) || 0, onChange: (n) => field.onChange(n) }) }) }), _jsx(FormMessage, {})] })) })] }) }) })] }) }, field.id))), _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "text-primary w-fit", onClick: () => {
|
|
208
|
+
appendLang({
|
|
209
|
+
id: undefined,
|
|
210
|
+
language: '',
|
|
211
|
+
reading: 0,
|
|
212
|
+
speaking: 0,
|
|
213
|
+
writing: 0,
|
|
214
|
+
});
|
|
215
|
+
}, children: [_jsx(Plus, { className: "h-4 w-4 mr-1" }), " Add more"] })] }) })] }), _jsxs("div", { children: [_jsx("h2", { className: "text-lg font-semibold mb-4", children: "References" }), _jsx(Form, { ...referencesForm, children: _jsxs("form", { className: "space-y-6", children: [refFields.length === 0 && _jsx(NoData, {}), refFields.map((field, index) => (_jsx(Accordion, { type: "single", collapsible: true, value: openReferenceAccordion, onValueChange: setOpenReferenceAccordion, children: _jsxs(AccordionItem, { value: `references-${index}`, className: "border-none", children: [_jsxs("div", { className: "flex items-center justify-between bg-primary/10 py-3 px-4 rounded-xl accordion-grow", children: [_jsx(AccordionTrigger, { className: "h-[50px] flex-grow hover:no-underline p-0", children: _jsx("div", { className: "flex items-center justify-between w-full", children: _jsxs("div", { className: "flex flex-col items-start", children: [_jsx("p", { className: "text-base font-semibold", children: field.reference_person || 'New Reference Entry' }), _jsx("p", { className: "text-sm text-text-secondary", children: field.position || 'Position' })] }) }) }), _jsx("div", { className: "pl-3 border-l border-black/60 ml-4", children: _jsx(Button, { variant: "link", type: "button", size: "sm", onClick: (e) => {
|
|
216
|
+
e.stopPropagation();
|
|
217
|
+
removeRef(index);
|
|
218
|
+
}, className: "h-8 w-8 p-0 hover:text-destructive", children: _jsx(Trash2, { size: 20, className: "text-text-primary hover:text-destructive" }) }) })] }), _jsx(AccordionContent, { children: _jsxs("div", { className: "p-4 pt-0 space-y-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(FormField, { control: referencesForm.control, name: `referenceEntries.${index}.reference_person`, render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "Reference Person" }), _jsx(FormControl, { children: _jsx(Input, { ...field, placeholder: "Enter reference person name" }) }), _jsx(FormMessage, {})] })) }), _jsx(FormField, { control: referencesForm.control, name: `referenceEntries.${index}.position`, render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "Position" }), _jsx(FormControl, { children: _jsx(Input, { ...field, placeholder: "Enter position" }) }), _jsx(FormMessage, {})] })) }), _jsx(FormField, { control: referencesForm.control, name: `referenceEntries.${index}.email`, render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "Email" }), _jsx(FormControl, { children: _jsx(Input, { ...field, type: "email", placeholder: "Enter email address" }) }), _jsx(FormMessage, {})] })) })] }), _jsx("div", { children: _jsx(FormField, { control: referencesForm.control, name: `referenceEntries.${index}.phone_number`, render: ({ field }) => (_jsxs(FormItem, { className: "w-full", children: [_jsx(FormLabel, { children: "Phone Number" }), _jsx(FormControl, { children: _jsxs("div", { className: "flex", children: [_jsx(Input, { value: "+977", readOnly: true, className: "w-[100px] shrink-0 rounded-tr-none rounded-br-none" }), _jsx(Input, { ...field, placeholder: "Enter phone number", className: "w-full border-l-0 rounded-tl-none rounded-bl-none" })] }) }), _jsx(FormMessage, {})] })) }) })] }) })] }) }, field.id))), _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "text-primary w-fit", onClick: () => {
|
|
219
|
+
appendRef({
|
|
220
|
+
id: undefined,
|
|
221
|
+
reference_person: '',
|
|
222
|
+
position: '',
|
|
223
|
+
email: '',
|
|
224
|
+
phone_number: '',
|
|
225
|
+
});
|
|
226
|
+
}, children: [_jsx(Plus, { className: "h-4 w-4 mr-1" }), " Add more"] })] }) })] })] }));
|
|
227
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EducationForm.d.ts","sourceRoot":"","sources":["../../../../src/components/cv-builder/forms/EducationForm.tsx"],"names":[],"mappings":"AA8CA,MAAM,CAAC,OAAO,UAAU,aAAa,4CAwVpC"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useCallback, useMemo } from 'react';
|
|
4
|
+
import { useForm, useFieldArray } from 'react-hook-form';
|
|
5
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form';
|
|
8
|
+
import { Input } from '@/components/ui/input';
|
|
9
|
+
import { Button } from '@/components/ui/button';
|
|
10
|
+
import { Checkbox } from '@/components/ui/checkbox';
|
|
11
|
+
import { useCvPreview } from '@/stores/cvPreview.store';
|
|
12
|
+
import { useCvDraftsStore } from '@/stores/cvDrafts.store';
|
|
13
|
+
import { Plus, Trash2 } from 'lucide-react';
|
|
14
|
+
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from '@/components/ui/accordion';
|
|
15
|
+
import NoData from '@/components/common/no-data';
|
|
16
|
+
const educationFormSchema = z.object({
|
|
17
|
+
id: z.string().optional(),
|
|
18
|
+
institution_name: z.string().min(1, { message: 'Institution Name is required' }),
|
|
19
|
+
course_name: z.string().min(1, { message: 'Course Name is required' }),
|
|
20
|
+
institution_address: z.string().optional(),
|
|
21
|
+
start: z.string().min(1, { message: 'Start date is required' }),
|
|
22
|
+
end: z.string().optional(),
|
|
23
|
+
still_studying: z.boolean().default(false),
|
|
24
|
+
gpa: z.string().optional(),
|
|
25
|
+
});
|
|
26
|
+
const educationFormArraySchema = z.object({
|
|
27
|
+
educationEntries: z.array(educationFormSchema).min(0),
|
|
28
|
+
});
|
|
29
|
+
export default function EducationForm() {
|
|
30
|
+
const { live, setSections } = useCvPreview();
|
|
31
|
+
const { getCurrentDraft, updateDraft } = useCvDraftsStore();
|
|
32
|
+
// Transform education from live data to form format
|
|
33
|
+
const educationList = useMemo(() => {
|
|
34
|
+
if (!live?.sections?.education || !Array.isArray(live.sections.education)) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
return live.sections.education.map((edu) => {
|
|
38
|
+
const startDate = edu.startDate
|
|
39
|
+
? new Date(edu.startDate).toISOString().split('T')[0]
|
|
40
|
+
: '';
|
|
41
|
+
const endDate = edu.endDate && !edu.current
|
|
42
|
+
? new Date(edu.endDate).toISOString().split('T')[0]
|
|
43
|
+
: '';
|
|
44
|
+
return {
|
|
45
|
+
id: edu.id || `edu_${Date.now()}`,
|
|
46
|
+
institution_name: edu.institution || '',
|
|
47
|
+
course_name: edu.field || edu.degree || '',
|
|
48
|
+
institution_address: edu.location || '',
|
|
49
|
+
start: startDate,
|
|
50
|
+
end: endDate,
|
|
51
|
+
still_studying: edu.current || false,
|
|
52
|
+
gpa: edu.gpa || '',
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
}, [live?.sections?.education]);
|
|
56
|
+
const form = useForm({
|
|
57
|
+
resolver: zodResolver(educationFormArraySchema),
|
|
58
|
+
defaultValues: {
|
|
59
|
+
educationEntries: educationList.length > 0 ? educationList : [],
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
const { fields, append, remove } = useFieldArray({
|
|
63
|
+
control: form.control,
|
|
64
|
+
name: 'educationEntries',
|
|
65
|
+
});
|
|
66
|
+
// Track if we've initialized to prevent reset loops
|
|
67
|
+
const initializedRef = useRef(false);
|
|
68
|
+
const prevEducationListRef = useRef('');
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
const currentListStr = JSON.stringify(educationList);
|
|
71
|
+
// Only reset if:
|
|
72
|
+
// 1. We haven't initialized yet AND there's data
|
|
73
|
+
// 2. OR the data actually changed from an external source (different structure)
|
|
74
|
+
if (!initializedRef.current && educationList.length > 0) {
|
|
75
|
+
form.reset({ educationEntries: educationList });
|
|
76
|
+
initializedRef.current = true;
|
|
77
|
+
prevEducationListRef.current = currentListStr;
|
|
78
|
+
}
|
|
79
|
+
else if (initializedRef.current && prevEducationListRef.current !== currentListStr) {
|
|
80
|
+
// Only reset if the structure changed significantly (e.g., loading a draft)
|
|
81
|
+
const prevList = JSON.parse(prevEducationListRef.current || '[]');
|
|
82
|
+
const currentIds = educationList.map(e => e.id).sort().join(',');
|
|
83
|
+
const prevIds = prevList.map((e) => e.id).sort().join(',');
|
|
84
|
+
// Only reset if IDs changed (new draft loaded) or count changed significantly
|
|
85
|
+
if (currentIds !== prevIds || Math.abs(educationList.length - prevList.length) > 0) {
|
|
86
|
+
form.reset({ educationEntries: educationList });
|
|
87
|
+
prevEducationListRef.current = currentListStr;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}, [educationList, form]);
|
|
91
|
+
// Helper function to format year from date
|
|
92
|
+
const getYearFromDate = (dateString) => {
|
|
93
|
+
if (!dateString)
|
|
94
|
+
return '';
|
|
95
|
+
return new Date(dateString).getFullYear().toString();
|
|
96
|
+
};
|
|
97
|
+
// Helper function to format the date range
|
|
98
|
+
const formatDateRange = (startDate, endDate) => {
|
|
99
|
+
const startYear = getYearFromDate(startDate);
|
|
100
|
+
const endYear = getYearFromDate(endDate);
|
|
101
|
+
if (!startYear && !endYear)
|
|
102
|
+
return '';
|
|
103
|
+
if (!endYear)
|
|
104
|
+
return `${startYear}-Present`;
|
|
105
|
+
if (!startYear)
|
|
106
|
+
return endYear;
|
|
107
|
+
return `${startYear}-${endYear}`;
|
|
108
|
+
};
|
|
109
|
+
// ---- LIVE PREVIEW: Education ----
|
|
110
|
+
const debRefEdu = useRef(null);
|
|
111
|
+
const mapEdu = (rows) => rows.map((r, i) => ({
|
|
112
|
+
id: String(r.id ?? `tmp-edu-${i}`),
|
|
113
|
+
institution: r.institution_name || '',
|
|
114
|
+
degree: r.course_name || '',
|
|
115
|
+
field: r.course_name || '',
|
|
116
|
+
location: r.institution_address || '',
|
|
117
|
+
startDate: r.start || '',
|
|
118
|
+
endDate: r.still_studying ? undefined : r.end || undefined,
|
|
119
|
+
current: r.still_studying ?? !r.end,
|
|
120
|
+
gpa: r.gpa || '',
|
|
121
|
+
honors: [],
|
|
122
|
+
}));
|
|
123
|
+
const pushEdu = useCallback((payload) => {
|
|
124
|
+
clearTimeout(debRefEdu.current);
|
|
125
|
+
debRefEdu.current = setTimeout(() => setSections({ education: payload }), 160);
|
|
126
|
+
}, [setSections]);
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
const sub = form.watch((_v, { name }) => {
|
|
129
|
+
if (!name?.startsWith('educationEntries'))
|
|
130
|
+
return;
|
|
131
|
+
pushEdu(mapEdu(form.getValues().educationEntries ?? []));
|
|
132
|
+
});
|
|
133
|
+
// bootstrap once for defaults
|
|
134
|
+
pushEdu(mapEdu(form.getValues().educationEntries ?? []));
|
|
135
|
+
return () => {
|
|
136
|
+
sub?.unsubscribe?.();
|
|
137
|
+
if (debRefEdu.current)
|
|
138
|
+
clearTimeout(debRefEdu.current);
|
|
139
|
+
};
|
|
140
|
+
}, [form, pushEdu]);
|
|
141
|
+
// Update draft when data changes
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
const currentDraft = getCurrentDraft();
|
|
144
|
+
if (currentDraft && live) {
|
|
145
|
+
updateDraft(currentDraft.id, {
|
|
146
|
+
cv_data: live,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}, [live, getCurrentDraft, updateDraft]);
|
|
150
|
+
return (_jsxs("div", { className: "mb-6", children: [_jsx("h1", { className: "text-2xl mb-2 font-medium", children: "Education" }), _jsx("p", { className: "text-sm mb-6", children: "Add your educational background. This information will appear on your CV." }), _jsx(Form, { ...form, children: _jsxs("form", { className: "space-y-6 pb-24 lg:pb-0", children: [fields.length === 0 && _jsx(NoData, {}), fields.map((field, index) => {
|
|
151
|
+
const dateRange = formatDateRange(field.start, field.end || '');
|
|
152
|
+
return (_jsx(Accordion, { type: "single", collapsible: true, children: _jsxs(AccordionItem, { value: `education-${index}`, className: "border-none", children: [_jsxs("div", { className: "flex items-center justify-between bg-primary/10 py-3 px-4 rounded-xl accordion-grow", children: [_jsx(AccordionTrigger, { className: "min-h-[70px] flex-grow hover:no-underline p-0 text-left items-start", children: _jsxs("div", { className: "flex flex-col items-start w-full", children: [_jsx("p", { className: "text-base font-semibold line-clamp-1", children: form.watch(`educationEntries.${index}.course_name`) || 'New Education Entry' }), _jsx("p", { className: "text-sm font-light line-clamp-1", children: form.watch(`educationEntries.${index}.institution_name`) }), dateRange && (_jsx("p", { className: "text-sm font-medium text-gray-600 line-clamp-1", children: dateRange }))] }) }), _jsx("div", { className: "pl-3 border-l border-black/60 ml-4", children: _jsx(Button, { variant: "link", type: "button", size: "sm", onClick: (e) => {
|
|
153
|
+
e.stopPropagation();
|
|
154
|
+
remove(index);
|
|
155
|
+
}, className: "h-8 w-8 p-0 hover:text-destructive", children: _jsx(Trash2, { size: 20, className: "text-text-primary hover:text-destructive" }) }) })] }), _jsx(AccordionContent, { children: _jsx("div", { className: "p-4 pt-0", children: _jsxs("div", { className: "space-y-4", children: [_jsx(FormField, { control: form.control, name: `educationEntries.${index}.institution_name`, render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "University" }), _jsx(FormControl, { children: _jsx(Input, { ...field, placeholder: "University Name" }) }), _jsx(FormMessage, {})] })) }), _jsx(FormField, { control: form.control, name: `educationEntries.${index}.course_name`, render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "Course" }), _jsx(FormControl, { children: _jsx(Input, { ...field, placeholder: "Course Name" }) }), _jsx(FormMessage, {})] })) }), _jsx(FormField, { control: form.control, name: `educationEntries.${index}.institution_address`, render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "Institution Address" }), _jsx(FormControl, { children: _jsx(Input, { ...field, placeholder: "Institution Address" }) }), _jsx(FormMessage, {})] })) }), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-4 mb-4", children: [_jsxs("div", { children: [_jsx(FormLabel, { className: "block text-sm font-medium mb-1", children: "Start Date" }), _jsx(FormField, { control: form.control, name: `educationEntries.${index}.start`, render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormControl, { children: _jsx(Input, { ...field, type: "date" }) }), _jsx(FormMessage, {})] })) })] }), _jsxs("div", { children: [_jsx(FormLabel, { className: "block text-sm font-medium mb-1", children: "End Date" }), _jsx(FormField, { control: form.control, name: `educationEntries.${index}.end`, render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormControl, { children: _jsx(Input, { ...field, type: "date", disabled: form.watch(`educationEntries.${index}.still_studying`) }) }), _jsx(FormMessage, {})] })) })] })] }), _jsx(FormField, { control: form.control, name: `educationEntries.${index}.still_studying`, render: ({ field }) => (_jsxs(FormItem, { className: "flex flex-row items-start space-x-3 space-y-0", children: [_jsx(FormControl, { children: _jsx(Checkbox, { checked: field.value, onCheckedChange: field.onChange }) }), _jsx("div", { className: "space-y-1 leading-none", children: _jsx(FormLabel, { children: "Still Studying" }) })] })) }), _jsx(FormField, { control: form.control, name: `educationEntries.${index}.gpa`, render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormLabel, { children: "GPA" }), _jsx(FormControl, { children: _jsx(Input, { ...field, placeholder: "3.8" }) }), _jsx(FormMessage, {})] })) })] }) }) })] }) }, field.id));
|
|
156
|
+
}), _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "text-blue-600", onClick: () => {
|
|
157
|
+
append({
|
|
158
|
+
id: undefined,
|
|
159
|
+
institution_name: '',
|
|
160
|
+
course_name: '',
|
|
161
|
+
institution_address: '',
|
|
162
|
+
start: '',
|
|
163
|
+
end: '',
|
|
164
|
+
still_studying: false,
|
|
165
|
+
gpa: '',
|
|
166
|
+
});
|
|
167
|
+
}, children: [_jsx(Plus, { className: "h-4 w-4 mr-1" }), " Add Education"] })] }) })] }));
|
|
168
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExperienceForm.d.ts","sourceRoot":"","sources":["../../../../src/components/cv-builder/forms/ExperienceForm.tsx"],"names":[],"mappings":"AAgGA,MAAM,CAAC,OAAO,UAAU,cAAc,4CAggBrC"}
|