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:
@@ -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')");
|
||||
|
||||
Reference in New Issue
Block a user