|
| 1 | +'use client' |
| 2 | + |
| 3 | +import Link from 'next/link' |
| 4 | +import { useEffect, useState, useCallback } from 'react' |
| 5 | + |
| 6 | +type ResumeData = { personal: Record<string, string>; experience: Array<Record<string, string>>; education: Array<Record<string, string>>; skills: string[]; languages: Array<Record<string, string>> } |
| 7 | + |
| 8 | +const PERSONAL_FIELDS = [ |
| 9 | + { key: 'nome', label: 'Nome' }, { key: 'cognome', label: 'Cognome' }, { key: 'email', label: 'Email' }, |
| 10 | + { key: 'telefono', label: 'Telefono' }, { key: 'citta', label: 'Città' }, { key: 'titolo', label: 'Titolo Professionale' }, |
| 11 | +]; |
| 12 | + |
| 13 | +function Field({ label, value, onChange }: { label: string; value: string; onChange: (v: string) => void }) { |
| 14 | + return ( |
| 15 | + <div className="flex flex-col gap-0.5"> |
| 16 | + <label className="text-[8px] font-bold tracking-widest text-[var(--color-dim)] uppercase">{label}</label> |
| 17 | + <input value={value} onChange={e => onChange(e.target.value)} className="text-[10px] px-2 py-1.5 rounded" style={{ background: 'var(--color-deep)', color: 'var(--color-muted)', border: '1px solid var(--color-border)' }} /> |
| 18 | + </div> |
| 19 | + ) |
| 20 | +} |
| 21 | + |
| 22 | +function SectionTitle({ title }: { title: string }) { |
| 23 | + return <p className="text-[9px] font-bold tracking-widest text-[var(--color-dim)] uppercase mt-4 mb-2 border-b border-[var(--color-border)] pb-1">{title}</p>; |
| 24 | +} |
| 25 | + |
| 26 | +export default function ResumeBuilderPage() { |
| 27 | + const [data, setData] = useState<ResumeData>({ personal: {}, experience: [], education: [], skills: [], languages: [] }) |
| 28 | + const [saving, setSaving] = useState(false) |
| 29 | + const [saved, setSaved] = useState(false) |
| 30 | + const [tab, setTab] = useState<'edit' | 'preview'>('edit') |
| 31 | + |
| 32 | + const fetchData = useCallback(async () => { |
| 33 | + const res = await fetch('/api/resume').catch(() => null); |
| 34 | + if (!res?.ok) return; |
| 35 | + const d = await res.json(); |
| 36 | + setData({ personal: d.personal ?? {}, experience: d.experience ?? [], education: d.education ?? [], skills: d.skills ?? [], languages: d.languages ?? [] }); |
| 37 | + }, []) |
| 38 | + |
| 39 | + useEffect(() => { fetchData() }, [fetchData]) |
| 40 | + |
| 41 | + const save = async () => { |
| 42 | + setSaving(true); |
| 43 | + await fetch('/api/resume', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }).catch(() => null); |
| 44 | + setSaving(false); setSaved(true); setTimeout(() => setSaved(false), 2000); |
| 45 | + } |
| 46 | + |
| 47 | + const updatePersonal = (key: string, val: string) => setData(d => ({ ...d, personal: { ...d.personal, [key]: val } })); |
| 48 | + const updateSkills = (val: string) => setData(d => ({ ...d, skills: val.split(',').map(s => s.trim()).filter(Boolean) })); |
| 49 | + |
| 50 | + return ( |
| 51 | + <div style={{ animation: 'fade-in 0.35s ease both' }}> |
| 52 | + <div className="mb-6 pb-4 border-b border-[var(--color-border)]"> |
| 53 | + <div className="flex items-center gap-2 mb-1"> |
| 54 | + <Link href="/dashboard" className="text-[10px] text-[var(--color-dim)] hover:text-[var(--color-muted)] no-underline transition-colors">Dashboard</Link> |
| 55 | + <span className="text-[var(--color-border)]">/</span> |
| 56 | + <span className="text-[10px] text-[var(--color-muted)]">Resume Builder</span> |
| 57 | + </div> |
| 58 | + <div className="flex items-center justify-between mt-3"> |
| 59 | + <h1 className="text-2xl font-bold tracking-tight text-[var(--color-white)]">Resume Builder</h1> |
| 60 | + <div className="flex gap-2"> |
| 61 | + <button onClick={() => setTab(tab === 'edit' ? 'preview' : 'edit')} className="px-3 py-1 rounded text-[10px] font-semibold cursor-pointer" style={{ background: 'var(--color-row)', color: 'var(--color-muted)', border: '1px solid var(--color-border)' }}>{tab === 'edit' ? 'Preview' : 'Modifica'}</button> |
| 62 | + <button onClick={save} disabled={saving} className="px-3 py-1 rounded text-[10px] font-bold cursor-pointer" style={{ background: saved ? 'var(--color-green)' : 'var(--color-row)', color: saved ? '#000' : 'var(--color-green)', border: '1px solid var(--color-green)40' }}>{saving ? 'Salvo...' : saved ? 'Salvato!' : 'Salva'}</button> |
| 63 | + </div> |
| 64 | + </div> |
| 65 | + </div> |
| 66 | + |
| 67 | + {tab === 'edit' ? ( |
| 68 | + <div className="border border-[var(--color-border)] rounded-lg bg-[var(--color-panel)] p-5"> |
| 69 | + <SectionTitle title="Dati Personali" /> |
| 70 | + <div className="grid grid-cols-3 gap-3">{PERSONAL_FIELDS.map(f => <Field key={f.key} label={f.label} value={data.personal[f.key] ?? ''} onChange={v => updatePersonal(f.key, v)} />)}</div> |
| 71 | + |
| 72 | + <SectionTitle title="Esperienza" /> |
| 73 | + {data.experience.map((exp, i) => ( |
| 74 | + <div key={i} className="flex gap-2 mb-2">{['azienda', 'ruolo', 'periodo'].map(k => <Field key={k} label={k} value={exp[k] ?? ''} onChange={v => { const e = [...data.experience]; e[i] = { ...e[i], [k]: v }; setData(d => ({ ...d, experience: e })); }} />)}</div> |
| 75 | + ))} |
| 76 | + |
| 77 | + <SectionTitle title="Formazione" /> |
| 78 | + {data.education.map((edu, i) => ( |
| 79 | + <div key={i} className="flex gap-2 mb-2">{['istituto', 'titolo', 'anno'].map(k => <Field key={k} label={k} value={edu[k] ?? ''} onChange={v => { const e = [...data.education]; e[i] = { ...e[i], [k]: v }; setData(d => ({ ...d, education: e })); }} />)}</div> |
| 80 | + ))} |
| 81 | + |
| 82 | + <SectionTitle title="Competenze" /> |
| 83 | + <Field label="Competenze (virgola)" value={data.skills.join(', ')} onChange={updateSkills} /> |
| 84 | + |
| 85 | + <SectionTitle title="Lingue" /> |
| 86 | + {data.languages.map((lng, i) => ( |
| 87 | + <div key={i} className="flex gap-2 mb-2">{['lingua', 'livello'].map(k => <Field key={k} label={k} value={lng[k] ?? ''} onChange={v => { const l = [...data.languages]; l[i] = { ...l[i], [k]: v }; setData(d => ({ ...d, languages: l })); }} />)}</div> |
| 88 | + ))} |
| 89 | + </div> |
| 90 | + ) : ( |
| 91 | + <div className="border border-[var(--color-border)] rounded-lg bg-[var(--color-panel)] p-6"> |
| 92 | + <div className="text-center mb-4 pb-3 border-b border-[var(--color-border)]"> |
| 93 | + <p className="text-[16px] font-bold text-[var(--color-white)]">{data.personal.nome} {data.personal.cognome}</p> |
| 94 | + <p className="text-[11px] text-[var(--color-green)] font-medium">{data.personal.titolo}</p> |
| 95 | + <p className="text-[9px] text-[var(--color-dim)] mt-1">{[data.personal.email, data.personal.telefono, data.personal.citta].filter(Boolean).join(' · ')}</p> |
| 96 | + </div> |
| 97 | + {data.experience.length > 0 && (<><p className="text-[9px] font-bold tracking-widest text-[var(--color-dim)] uppercase mb-2">Esperienza</p> |
| 98 | + {data.experience.map((e, i) => <div key={i} className="mb-2"><p className="text-[11px] text-[var(--color-bright)] font-medium">{e.ruolo} — {e.azienda}</p><p className="text-[9px] text-[var(--color-dim)]">{e.periodo}</p>{e.descrizione && <p className="text-[9px] text-[var(--color-muted)] mt-0.5">{e.descrizione}</p>}</div>)}</>)} |
| 99 | + {data.education.length > 0 && (<><p className="text-[9px] font-bold tracking-widest text-[var(--color-dim)] uppercase mt-3 mb-2">Formazione</p> |
| 100 | + {data.education.map((e, i) => <div key={i} className="mb-1"><p className="text-[11px] text-[var(--color-bright)]">{e.titolo} — {e.istituto}</p><p className="text-[9px] text-[var(--color-dim)]">{e.anno}</p></div>)}</>)} |
| 101 | + {data.skills.length > 0 && (<><p className="text-[9px] font-bold tracking-widest text-[var(--color-dim)] uppercase mt-3 mb-2">Competenze</p> |
| 102 | + <div className="flex flex-wrap gap-1">{data.skills.map(s => <span key={s} className="text-[9px] px-2 py-0.5 rounded" style={{ color: 'var(--color-green)', border: '1px solid var(--color-green)30', background: 'rgba(0,200,83,0.08)' }}>{s}</span>)}</div></>)} |
| 103 | + {data.languages.length > 0 && (<><p className="text-[9px] font-bold tracking-widest text-[var(--color-dim)] uppercase mt-3 mb-2">Lingue</p> |
| 104 | + {data.languages.map((l, i) => <span key={i} className="text-[10px] text-[var(--color-muted)] mr-3">{l.lingua}: {l.livello}</span>)}</>)} |
| 105 | + <div className="mt-4 pt-3 border-t border-[var(--color-border)]"> |
| 106 | + <button className="px-3 py-1.5 rounded text-[10px] font-bold cursor-pointer" style={{ background: 'var(--color-row)', color: 'var(--color-dim)', border: '1px solid var(--color-border)' }}>Export PDF (coming soon)</button> |
| 107 | + </div> |
| 108 | + </div> |
| 109 | + )} |
| 110 | + </div> |
| 111 | + ) |
| 112 | +} |
0 commit comments