// Prodz — Profile (own + others). Tabs: Prods · Moments · Gear · (Shoutouts, me) · About.
// Others: Follow / Collab / Message + Recommend (after following). "Me" exposes
// language, Saved items, and the Shoutouts (job calls) you posted — review, link, edit.
function PersonProfile({ id }) {
const { L, lang, setLang } = useL();
const nav = useNav();
const { Avatar, Button, Tabs, Tag, StatusDot } = window.ProdzDesignSystem_d0b87b;
const isMe = id === 'me';
const p = PEOPLE[id] || PEOPLE.me;
const [tab, setTab] = useStateS('prods');
const [following, setFollowing] = useStateS(false);
const [showRec, setShowRec] = useStateS(false);
const [stars, setStars] = useStateS(5);
const [review, setReview] = useStateS('');
const [statSheet, setStatSheet] = useStateS(null); // 'network' | 'collab' | 'recs'
const rec = nav.recOf(id);
const recAvg = rec.count ? (rec.sum / rec.count).toFixed(1) : '—';
const gear = [
{ name: 'Sony FX6', kind: { it: 'Cinema camera', en: 'Cinema camera' }, sn: 'FX6-0042', img: IMG.camera, ready: true },
{ name: 'Sigma 85mm f/1.4', kind: { it: 'Obiettivo', en: 'Lens' }, sn: 'ART-1185', img: IMG.lens, ready: true },
{ name: 'Aputure 600d', kind: { it: 'Illuminazione', en: 'Lighting' }, sn: 'AP-600-7', img: null, ready: false }];
const specialties = ['EDITORIAL', 'LOW LIGHT', 'FASHION', 'COMMERCIAL'];
const interests = ['Moda', 'Ritratto', 'Commercial'];
const submitRec = () => {
nav.recommend(id, stars, review);
setShowRec(false);
nav.toast(L('Raccomandazione inviata · grazie!', 'Recommendation sent · thanks!'), null);
setStars(5);setReview('');
};
// people lists behind the stat tiles
const followingList = (id === 'me' ? FOLLOWING : Object.keys(PEOPLE).filter((x) => x !== id && x !== 'me')).filter((x) => PEOPLE[x]);
const collabList = collaboratorsOf(id);
const recEntries = rec.entries || [];
const tabs = [
{ id: 'prods', label: L('Prod', 'Prods') },
{ id: 'moments', label: 'Moments' },
{ id: 'gear', label: 'Gear' },
...(isMe ? [{ id: 'shouts', label: 'Shoutout' }] : []),
{ id: 'about', label: L('Info', 'About') }];
return (
{/* cover + avatar */}
{isMe ?
nav.openSaved()} aria-label={L('Elementi salvati', 'Saved items')} style={{ width: 38, height: 38, borderRadius: '50%', border: 'none', cursor: 'pointer', background: 'var(--surface-raised)', boxShadow: 'var(--ring-inset-hairline)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
:
}
onClick={() => setFollowing((v) => !v)}>{following ? L('Segui già', 'Following') : L('Segui', 'Follow')}
} onClick={() => nav.sendCollab(p)}>Collab
}
{p.name}
{p.online && }
{tr(L, p.role)} · {p.city}
{/* Recommend — appears once you follow this person (feeds their Recs stat) */}
{!isMe && following &&
}
onClick={() => setShowRec(true)}>{L('Raccomanda', 'Recommend')}
}
{/* Saved items entry — own profile */}
{isMe &&
nav.openSaved()} style={{ display: 'flex', alignItems: 'center', gap: 12, width: '100%', marginTop: 14, padding: '12px 14px', border: 'none', cursor: 'pointer', textAlign: 'left', borderRadius: 'var(--radius-md)', background: 'var(--surface-card)', boxShadow: 'var(--ring-inset-hairline)' }}>
{L('Elementi salvati', 'Saved items')}
{L('Produzioni, location e gear in moodboard', 'Productions, locations & gear in moodboards')}
}
{/* stats — tap to see the people / recommendations behind each number */}
setTab('prods')} />
setStatSheet('collab')} />
setStatSheet('network')} />
setStatSheet('recs')} />
{/* tabs */}
{tab === 'prods' &&
{PRODUCTIONS.concat(PRODUCTIONS.slice(0, 1)).map((pr, i) =>
nav.openProduction(pr.id)} style={{ position: 'relative', height: 130, padding: 0, border: 'none', cursor: 'pointer', borderRadius: 'var(--radius-md)', overflow: 'hidden', background: `url(${pr.image}) center/cover`, boxShadow: 'var(--ring-inset-hairline)' }}>
{pr.title}
{tr(L, pr.meta)}
)}
}
{tab === 'moments' &&
}
{tab === 'gear' &&
{L('Attrezzatura e disponibilità sul set.', 'Toolkit & on-set availability.')}
{gear.map((g) =>
{!g.img && }
{g.name}
{tr(L, g.kind)} · SN {g.sn}
)}
}
{tab === 'shouts' && isMe &&
{L('Le job call e collab che hai pubblicato — rivedi, collega ed edita.', 'The job calls & collabs you posted — review, link and edit.')}
{MY_SHOUTOUTS.map((s) => {
const prod = PRODUCTIONS.find((x) => x.id === s.prod);
const open = s.status === 'open';
return (
{s.kind === 'job' ? L('Job Call', 'Job Call') : 'Collab'}
{s.paid && {L('Retribuito', 'Paid')} }
{open ? L('Aperta', 'Open') : L('Chiusa', 'Closed')}
{tr(L, s.title)}
{prod.title}
{tr(L, s.date)}
{s.applicants}
} onClick={() => nav.openInbox()}>{L('Candidature', 'Applicants')}
} onClick={() => nav.openProduction(s.prod)} aria-label={L('Collega', 'Link')}>{L('Collega', 'Link')}
} onClick={() => nav.toast(L('Apertura editor…', 'Opening editor…'), null)} aria-label={L('Modifica', 'Edit')}>{L('Modifica', 'Edit')}
);
})}
}
{tab === 'about' &&
{L('Biografia', 'Biography')}
{L('Direttrice della fotografia con base a Milano. Lavoro su moda editoriale e commercial, con un approccio a naturali motivati e luci pratiche.', 'Cinematographer based in Milan. I work across editorial fashion and commercial with a motivated-naturals, practical-light approach.')}
{L('Specialità', 'Specialties')}
{specialties.map((s) => {s} )}
{isMe &&
{L('Dal tuo profilo', 'From your profile')}
}
}
{/* Recommend sheet — stars (1–5) + review feed the Recs average */}
setShowRec(false)}>
{L('Raccomanda', 'Recommend')}
{p.name}
{L('Il tuo voto si somma e media nella sua statistica “Recs”.', 'Your rating sums and averages into their “Recs” stat.')}
{[1, 2, 3, 4, 5].map((n) =>
setStars(n)} aria-label={n + ' stars'} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 4 }}>
)}
{/* Network stat → people in your network (those you follow) */}
setStatSheet(null)}>
{followingList.map((pid) =>
{ setStatSheet(null); nav.openProfile(pid); }} L={L} />)}
{/* Collab stat → people you've collaborated with */}
setStatSheet(null)}>
{collabList.map((c) => {
const prods = c.prods.map((pid) => { const pr = PRODUCTIONS.find((x) => x.id === pid); return pr ? pr.title : null; }).filter(Boolean);
return
{ setStatSheet(null); nav.openProfile(c.id); }} L={L}
context={prods.length ? prods.join(' · ') : L('Collaborazione', 'Collaboration')} contextIcon="Clapperboard" />;
})}
{/* Recs stat → who recommended this person, with the content */}
setStatSheet(null)}>
{recEntries.length === 0 ?
{L('Ancora nessuna raccomandazione con recensione.', 'No written recommendations yet.')}
:
{recEntries.map((e, i) => {
const person = PEOPLE[e.by] || PEOPLE.me;
return (
{person.name}{e.by === 'me' && L(' · Tu', ' · You')}
{[1, 2, 3, 4, 5].map((n) => )}
{e.text &&
{tr(L, e.text)}
}
{tr(L, e.time)}
);
})}
}
);
}
// Shared header for the stat detail sheets.
function StatSheetHead({ icon, tint, eyebrow, title }) {
return (
);
}
// One person row inside a stat sheet.
function PersonStatRow({ pid, onTap, L, context, contextIcon }) {
const { Avatar } = window.ProdzDesignSystem_d0b87b;
const p = PEOPLE[pid];
if (!p) return null;
return (
{p.name}
{context ? : null}
{context || `${tr(L, p.role)} · ${p.city}`}
);
}
const statListWrap = { display: 'flex', flexDirection: 'column', gap: 8, maxHeight: 380, overflowY: 'auto' };
// Stat tile — tappable to open the people / recommendations behind the number.
function StatTileX({ value, suffix, label, onClick, accent }) {
const interactive = !!onClick;
return (
{ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick(); } } : undefined}
style={{ position: 'relative', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 2, padding: '12px 6px', background: 'var(--teal-surface)', borderRadius: 'var(--radius-md)', boxShadow: 'var(--ring-teal)', cursor: interactive ? 'pointer' : 'default', WebkitTapHighlightColor: 'transparent' }}>
{value}{suffix && {suffix} }
{label}{interactive && }
);
}
function ProfileRow({ icon, label, value }) {
return (
{label}
{value}
);
}
Object.assign(window, { PersonProfile, ProfileRow, StatTileX, StatSheetHead, PersonStatRow });