// Screen-level views
const { useState, useEffect, useRef, useMemo } = React;
// ============================================================
// Team & Files (dashboard side cards)
// ============================================================
const TEAM = [
{
name: 'Joe Marshall', role: 'Head of Client Strategy', hue: 200, src: 'assets/team/joe.png',
status: 'online', meta: 'Responds in ~1 hr', primary: true,
email: 'joe@clicky.co.uk', phone: '0161 808 1888',
reportsTo: null, joined: '2018',
focus: ['Strategy', 'Quarterly reviews', 'Roadmap', 'Exec stakeholder'],
bio: 'Owns the relationship end-to-end. Sets quarterly direction with you and steers the delivery team day-to-day.',
qualifications: [
{ id: 'q-jm-1', name: 'Google Ads Search Certification', issuer: 'Google', issuerSlug: 'google', issued: 'Aug 2024', verified: true, source: 'google-api' },
{ id: 'q-jm-2', name: 'Meta Blueprint — Buying Professional', issuer: 'Meta', issuerSlug: 'meta', issued: 'Mar 2025', verified: true, source: 'meta-api' },
{ id: 'q-jm-3', name: 'LinkedIn Marketing Solutions Fundamentals', issuer: 'LinkedIn', issuerSlug: 'linkedin', issued: 'Jan 2024', verified: true, source: 'linkedin-api' },
{ id: 'q-jm-4', name: 'HubSpot Inbound Marketing', issuer: 'HubSpot', issuerSlug: 'hubspot', issued: 'Sep 2023', verified: false, source: 'manual' },
],
},
{
name: 'Matt Jones', role: 'PPC Team Lead', hue: 30, src: 'assets/team/matt.png',
status: 'online', meta: 'Google Ads · Bing',
email: 'matt@clicky.co.uk', phone: '0161 808 1888',
reportsTo: 'Joe Marshall', joined: '2019',
focus: ['Google Ads', 'PMax', 'Bing', 'Bidding strategy'],
bio: 'Leads the paid media pod. Hands-on with bidding, structure and channel mix decisions for ownership + holiday breaks.',
qualifications: [
{ id: 'q-mj-1', name: 'Google Ads Search Certification', issuer: 'Google', issuerSlug: 'google', issued: 'Feb 2025', verified: true, source: 'google-api' },
{ id: 'q-mj-2', name: 'Google Ads Display Certification', issuer: 'Google', issuerSlug: 'google', issued: 'Feb 2025', verified: true, source: 'google-api' },
{ id: 'q-mj-3', name: 'Google Ads Performance Max', issuer: 'Google', issuerSlug: 'google', issued: 'Mar 2025', verified: true, source: 'google-api' },
{ id: 'q-mj-4', name: 'Google Ads Shopping Certification', issuer: 'Google', issuerSlug: 'google', issued: 'Feb 2025', verified: true, source: 'google-api' },
{ id: 'q-mj-5', name: 'Microsoft Advertising Certified Pro', issuer: 'Microsoft', issuerSlug: 'microsoft', issued: 'Nov 2024', verified: true, source: 'manual' },
],
},
{
name: 'Nathan Smith', role: 'Agency Systems Manager', hue: 280, src: 'assets/team/nathan.png',
status: 'online', meta: 'GA4 · Looker',
email: 'nathan@clicky.co.uk', phone: '0161 808 1888',
reportsTo: 'Joe Marshall', joined: '2020',
focus: ['GA4', 'Looker Studio', 'Tag manager', 'Conversion tracking'],
bio: 'Keeps measurement honest — ships GA4, Looker and conversion-tracking work, and audits data quality before reports go out.',
qualifications: [
{ id: 'q-ns-1', name: 'Google Analytics Individual Qualification (GAIQ)', issuer: 'Google', issuerSlug: 'google', issued: 'Jan 2025', verified: true, source: 'google-api' },
{ id: 'q-ns-2', name: 'Google Tag Manager Fundamentals', issuer: 'Google', issuerSlug: 'google', issued: 'Jan 2025', verified: true, source: 'google-api' },
{ id: 'q-ns-3', name: 'Looker Studio Foundations', issuer: 'Google', issuerSlug: 'google', issued: 'Apr 2024', verified: true, source: 'google-api' },
{ id: 'q-ns-4', name: 'Hotjar Foundations', issuer: 'Hotjar', issuerSlug: 'manual', issued: 'Oct 2023', verified: false, source: 'manual' },
],
},
{
name: 'Hayley Roberts', role: 'UX Research & Design Manager', hue: 340, src: 'assets/team/hayley.png',
status: 'away', meta: 'Ads · Landing pages',
email: 'hayley@clicky.co.uk', phone: '0161 808 1888',
reportsTo: 'Joe Marshall', joined: '2021',
focus: ['Ad creative', 'Landing pages', 'CRO', 'Brand'],
bio: 'Runs design + research. Owns ad creative, landing-page concepts and the CRO experiment backlog.',
qualifications: [
{ id: 'q-hr-1', name: 'Nielsen Norman UX Certification', issuer: 'NN/g', issuerSlug: 'manual', issued: 'Jul 2023', verified: true, source: 'manual' },
{ id: 'q-hr-2', name: 'Optimizely Certified Expert', issuer: 'Optimizely', issuerSlug: 'manual', issued: 'Sep 2024', verified: true, source: 'manual' },
{ id: 'q-hr-3', name: 'Meta Blueprint — Creative Strategy', issuer: 'Meta', issuerSlug: 'meta', issued: 'May 2024', verified: true, source: 'meta-api' },
],
},
{
name: 'Shannon May', role: 'Senior Paid Media Specialist', hue: 150, src: 'assets/team/shannon.png',
status: 'offline', meta: 'Back tomorrow 09:00',
email: 'shannon@clicky.co.uk', phone: '0161 808 1888',
reportsTo: 'Matt Jones', joined: '2022',
focus: ['Day-to-day Google Ads', 'Search', 'Reporting cadence'],
bio: 'Hands-on day-to-day operator on the Evergreen accounts. First to spot anomalies and triage with Matt.',
qualifications: [
{ id: 'q-sm-1', name: 'Google Ads Search Certification', issuer: 'Google', issuerSlug: 'google', issued: 'Mar 2025', verified: true, source: 'google-api' },
{ id: 'q-sm-2', name: 'Google Ads Display Certification', issuer: 'Google', issuerSlug: 'google', issued: 'Mar 2025', verified: true, source: 'google-api' },
{ id: 'q-sm-3', name: 'Meta Blueprint Foundations', issuer: 'Meta', issuerSlug: 'meta', issued: 'Feb 2025', verified: true, source: 'meta-api' },
{ id: 'q-sm-4', name: 'TikTok Ads Specialist', issuer: 'TikTok', issuerSlug: 'tiktok', issued: 'Apr 2025', verified: false, source: 'manual' },
],
},
];
// Issuer brand-mark lookup. Used by the qualifications row everywhere
// (TeamProfilePanel + AM profile editor). Falls back to a generic certificate
// glyph for issuers without an SVG asset.
const ISSUER_LOGO_SRC = {
google: 'assets/google.svg',
meta: 'assets/meta.svg',
microsoft: 'assets/microsoft.svg',
linkedin: 'assets/linkedin.svg',
tiktok: 'assets/tiktok.svg',
reddit: 'assets/reddit.svg',
};
const ISSUER_HUE = {
google: '#4285f4', meta: '#0866ff', microsoft: '#00a4ef', tiktok: '#000000',
linkedin: '#0a66c2', hubspot: '#ff7a59', manual: '#52525b',
};
const IssuerMark = ({ slug, size = 20 }) => {
const src = ISSUER_LOGO_SRC[slug];
const hue = ISSUER_HUE[slug] || ISSUER_HUE.manual;
return (
{src ? (
) : (
)}
);
};
// Qualifications storage — overlays defaults from TEAM with anything the
// AM has saved/edited. Returns the resolved list for a given person.
const getTeamQualifications = (name) => {
try {
const saved = JSON.parse(localStorage.getItem('clicky_team_quals') || '{}');
if (Array.isArray(saved[name])) return saved[name];
} catch (e) {}
const t = TEAM.find(p => p.name === name);
return (t && t.qualifications) || [];
};
const setTeamQualifications = (name, list) => {
try {
const saved = JSON.parse(localStorage.getItem('clicky_team_quals') || '{}');
saved[name] = list;
localStorage.setItem('clicky_team_quals', JSON.stringify(saved));
} catch (e) {}
};
// Reusable qualifications list — used by TeamProfilePanel (read-only)
// and AmProfileScreen (with onEdit / onRemove handlers passed in).
const QualificationsList = ({ qualifications, onEdit, onRemove }) => {
if (!qualifications || qualifications.length === 0) {
return (
| onSort && onSort(sortKey)} style={{ padding: '12px 14px', textAlign: align, fontSize: 11, fontWeight: 600, color: isActive ? 'var(--text)' : 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', borderBottom: '1px solid var(--border)', cursor: 'pointer', userSelect: 'none', transition: 'color 120ms ease', whiteSpace: 'nowrap', ...(width != null ? { width } : {}), }} onMouseEnter={e => e.currentTarget.style.color = 'var(--text)'} onMouseLeave={e => e.currentTarget.style.color = isActive ? 'var(--text)' : 'var(--text-muted)'} > {children} {isActive ? ( dir === 'asc' ? : ) : ( )} | ); }; const StatusDot = ({ status }) => { const c = status === 'online' ? 'var(--positive)' : status === 'away' ? 'var(--warning)' : 'var(--text-faint)'; return ; }; const TeamCard = ({ setActive }) => { return (
|---|
| {c.name} | £{c.spend.toFixed(2)} | {c.clicks} | {c.conv} | £{c.cpc.toFixed(2)} | {c.ctr.toFixed(1)}% |
Strong April. Bookings closed at 49 — up +22.5% month-on-month — at an all-in cost-per-booking of £31.65. Branded search continues to do the heavy lifting; Performance Max picked up share through the second half of the month after a structure refresh.
| setSelected(e.target.checked ? visible.map(r => r.id) : [])}/> | {[ { key: 'name', label: 'Report' }, { key: 'channel', label: 'Channel' }, { key: 'period', label: 'Period' }, { key: 'status', label: 'Status' }, { key: 'uploadedBy', label: 'Built by' }, ].map(c => (||||||
|---|---|---|---|---|---|---|
| e.stopPropagation()}> setSelected(s => checked ? s.filter(x => x !== r.id) : [...s, r.id])}/> |
{isReady ?
{r.name}
{r.id}
{isReady && r.format && (
<>
·
{r.format} · {r.size}
>
)}
{!isReady && (
<>
·
Requested {r.requestedOn}
>
)}
|
{I.google}{r.channel} | {r.period} |
{isReady
? |
{isReady ? (
{r.uploadedBy}
Uploaded {r.uploadedOn}
|
e.stopPropagation()}>
{isReady ? (
|
| {inv.id} | {inv.date} | {inv.period} | {inv.due} | {inv.amount} |
|
|
| {t.id} | {t.subject} | {pm.icon} {t.priority} | {sm.icon} {t.status} |
|
{t.opened} |
Type a question or pick a starter — your answer will appear in the full Ask page.
Ask anything about your accounts. I have context on the last 30 days of performance.
Hi {CLIENT.contact?.split(' ')[0] || 'there'} — quick rundown on what ClickyAI™ is good at, and when you should just ping me instead.
Verify any critical numbers in your reports — and if a reply doesn't sit right, tell me. ClickyAI gets sharper the more we correct it.