feat: add trainer bio (experience, victories, education) across all layers

- Extend TeamMember type with experience/victories/education string arrays
- Add DB columns with auto-migration for existing databases
- Update API POST route to accept bio fields
- Add ListField component for editing string arrays in admin
- Add bio section (Опыт/Достижения/Образование) to team member admin form
- Create TeamProfile component with full profile view (photo + bio sections)
- Add "Подробнее" button to TeamMemberInfo that toggles to profile view

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 13:09:28 +03:00
parent ed90cd5924
commit 921d10800b
8 changed files with 282 additions and 14 deletions

View File

@@ -33,11 +33,21 @@ function initTables(db: Database.Database) {
image TEXT NOT NULL,
instagram TEXT,
description TEXT,
experience TEXT,
victories TEXT,
education TEXT,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
);
`);
// Migrate: add bio columns if missing
const cols = db.prepare("PRAGMA table_info(team_members)").all() as { name: string }[];
const colNames = new Set(cols.map((c) => c.name));
if (!colNames.has("experience")) db.exec("ALTER TABLE team_members ADD COLUMN experience TEXT");
if (!colNames.has("victories")) db.exec("ALTER TABLE team_members ADD COLUMN victories TEXT");
if (!colNames.has("education")) db.exec("ALTER TABLE team_members ADD COLUMN education TEXT");
}
// --- Sections ---
@@ -67,9 +77,17 @@ interface TeamMemberRow {
image: string;
instagram: string | null;
description: string | null;
experience: string | null;
victories: string | null;
education: string | null;
sort_order: number;
}
function parseJsonArray(val: string | null): string[] | undefined {
if (!val) return undefined;
try { const arr = JSON.parse(val); return Array.isArray(arr) && arr.length > 0 ? arr : undefined; } catch { return undefined; }
}
export function getTeamMembers(): (TeamMember & { id: number })[] {
const db = getDb();
const rows = db
@@ -82,6 +100,9 @@ export function getTeamMembers(): (TeamMember & { id: number })[] {
image: r.image,
instagram: r.instagram ?? undefined,
description: r.description ?? undefined,
experience: parseJsonArray(r.experience),
victories: parseJsonArray(r.victories),
education: parseJsonArray(r.education),
}));
}
@@ -100,6 +121,9 @@ export function getTeamMember(
image: r.image,
instagram: r.instagram ?? undefined,
description: r.description ?? undefined,
experience: parseJsonArray(r.experience),
victories: parseJsonArray(r.victories),
education: parseJsonArray(r.education),
};
}
@@ -112,8 +136,8 @@ export function createTeamMember(
.get() as { max: number };
const result = db
.prepare(
`INSERT INTO team_members (name, role, image, instagram, description, sort_order)
VALUES (?, ?, ?, ?, ?, ?)`
`INSERT INTO team_members (name, role, image, instagram, description, experience, victories, education, sort_order)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
)
.run(
data.name,
@@ -121,6 +145,9 @@ export function createTeamMember(
data.image,
data.instagram ?? null,
data.description ?? null,
data.experience?.length ? JSON.stringify(data.experience) : null,
data.victories?.length ? JSON.stringify(data.victories) : null,
data.education?.length ? JSON.stringify(data.education) : null,
maxOrder.max + 1
);
return result.lastInsertRowid as number;
@@ -139,6 +166,9 @@ export function updateTeamMember(
if (data.image !== undefined) { fields.push("image = ?"); values.push(data.image); }
if (data.instagram !== undefined) { fields.push("instagram = ?"); values.push(data.instagram || null); }
if (data.description !== undefined) { fields.push("description = ?"); values.push(data.description || null); }
if (data.experience !== undefined) { fields.push("experience = ?"); values.push(data.experience?.length ? JSON.stringify(data.experience) : null); }
if (data.victories !== undefined) { fields.push("victories = ?"); values.push(data.victories?.length ? JSON.stringify(data.victories) : null); }
if (data.education !== undefined) { fields.push("education = ?"); values.push(data.education?.length ? JSON.stringify(data.education) : null); }
if (fields.length === 0) return;
fields.push("updated_at = datetime('now')");