feat: add short description for team carousel cards
- Add shortDescription field to TeamMember type - DB migration #11: add short_description column to team_members - Admin editor: separate "Краткое описание (для карточки)" and "Полное описание" - Carousel shows shortDescription with line-clamp-3, falls back to description - Full description shown in "Подробнее" profile view Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ interface MemberForm {
|
|||||||
role: string;
|
role: string;
|
||||||
image: string;
|
image: string;
|
||||||
instagram: string;
|
instagram: string;
|
||||||
|
shortDescription: string;
|
||||||
description: string;
|
description: string;
|
||||||
experience: string[];
|
experience: string[];
|
||||||
victories: VictoryItem[];
|
victories: VictoryItem[];
|
||||||
@@ -36,6 +37,7 @@ export default function TeamMemberEditorPage() {
|
|||||||
role: "",
|
role: "",
|
||||||
image: "/images/team/placeholder.webp",
|
image: "/images/team/placeholder.webp",
|
||||||
instagram: "",
|
instagram: "",
|
||||||
|
shortDescription: "",
|
||||||
description: "",
|
description: "",
|
||||||
experience: [],
|
experience: [],
|
||||||
victories: [],
|
victories: [],
|
||||||
@@ -116,6 +118,7 @@ export default function TeamMemberEditorPage() {
|
|||||||
role: member.role,
|
role: member.role,
|
||||||
image: member.image,
|
image: member.image,
|
||||||
instagram: username,
|
instagram: username,
|
||||||
|
shortDescription: member.shortDescription || "",
|
||||||
description: member.description || "",
|
description: member.description || "",
|
||||||
experience: member.experience || [],
|
experience: member.experience || [],
|
||||||
victories: member.victories || [],
|
victories: member.victories || [],
|
||||||
@@ -302,7 +305,14 @@ export default function TeamMemberEditorPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<TextareaField
|
<TextareaField
|
||||||
label="Описание"
|
label="Краткое описание (для карточки)"
|
||||||
|
value={data.shortDescription}
|
||||||
|
onChange={(v) => setData({ ...data, shortDescription: v })}
|
||||||
|
rows={2}
|
||||||
|
placeholder="1-2 предложения для карусели"
|
||||||
|
/>
|
||||||
|
<TextareaField
|
||||||
|
label="Полное описание (для страницы тренера)"
|
||||||
value={data.description}
|
value={data.description}
|
||||||
onChange={(v) => setData({ ...data, description: v })}
|
onChange={(v) => setData({ ...data, description: v })}
|
||||||
rows={6}
|
rows={6}
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ export function TeamMemberInfo({ members, activeIndex, onSelect, onOpenBio }: Te
|
|||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{member.description && (
|
{(member.shortDescription || member.description) && (
|
||||||
<p className="mt-3 text-sm leading-relaxed text-white/55 line-clamp-3">
|
<p className="mt-3 text-sm leading-relaxed text-white/55 line-clamp-3">
|
||||||
{member.description}
|
{member.shortDescription || member.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -219,6 +219,16 @@ const migrations: Migration[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
version: 11,
|
||||||
|
name: "add_team_short_description",
|
||||||
|
up: (db) => {
|
||||||
|
const cols = db.prepare("PRAGMA table_info(team_members)").all() as { name: string }[];
|
||||||
|
if (!cols.some((c) => c.name === "short_description")) {
|
||||||
|
db.exec("ALTER TABLE team_members ADD COLUMN short_description TEXT");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function runMigrations(db: Database.Database) {
|
function runMigrations(db: Database.Database) {
|
||||||
@@ -278,6 +288,7 @@ interface TeamMemberRow {
|
|||||||
role: string;
|
role: string;
|
||||||
image: string;
|
image: string;
|
||||||
instagram: string | null;
|
instagram: string | null;
|
||||||
|
short_description: string | null;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
experience: string | null;
|
experience: string | null;
|
||||||
victories: string | null;
|
victories: string | null;
|
||||||
@@ -327,6 +338,7 @@ export function getTeamMembers(): (TeamMember & { id: number })[] {
|
|||||||
role: r.role,
|
role: r.role,
|
||||||
image: r.image,
|
image: r.image,
|
||||||
instagram: r.instagram ?? undefined,
|
instagram: r.instagram ?? undefined,
|
||||||
|
shortDescription: r.short_description ?? undefined,
|
||||||
description: r.description ?? undefined,
|
description: r.description ?? undefined,
|
||||||
experience: parseJsonArray(r.experience),
|
experience: parseJsonArray(r.experience),
|
||||||
victories: parseVictories(r.victories),
|
victories: parseVictories(r.victories),
|
||||||
@@ -348,6 +360,7 @@ export function getTeamMember(
|
|||||||
role: r.role,
|
role: r.role,
|
||||||
image: r.image,
|
image: r.image,
|
||||||
instagram: r.instagram ?? undefined,
|
instagram: r.instagram ?? undefined,
|
||||||
|
shortDescription: r.short_description ?? undefined,
|
||||||
description: r.description ?? undefined,
|
description: r.description ?? undefined,
|
||||||
experience: parseJsonArray(r.experience),
|
experience: parseJsonArray(r.experience),
|
||||||
victories: parseVictories(r.victories),
|
victories: parseVictories(r.victories),
|
||||||
@@ -364,14 +377,15 @@ export function createTeamMember(
|
|||||||
.get() as { max: number };
|
.get() as { max: number };
|
||||||
const result = db
|
const result = db
|
||||||
.prepare(
|
.prepare(
|
||||||
`INSERT INTO team_members (name, role, image, instagram, description, experience, victories, education, sort_order)
|
`INSERT INTO team_members (name, role, image, instagram, short_description, description, experience, victories, education, sort_order)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||||
)
|
)
|
||||||
.run(
|
.run(
|
||||||
data.name,
|
data.name,
|
||||||
data.role,
|
data.role,
|
||||||
data.image,
|
data.image,
|
||||||
data.instagram ?? null,
|
data.instagram ?? null,
|
||||||
|
data.shortDescription ?? null,
|
||||||
data.description ?? null,
|
data.description ?? null,
|
||||||
data.experience?.length ? JSON.stringify(data.experience) : null,
|
data.experience?.length ? JSON.stringify(data.experience) : null,
|
||||||
data.victories?.length ? JSON.stringify(data.victories) : null,
|
data.victories?.length ? JSON.stringify(data.victories) : null,
|
||||||
@@ -393,6 +407,7 @@ export function updateTeamMember(
|
|||||||
if (data.role !== undefined) { fields.push("role = ?"); values.push(data.role); }
|
if (data.role !== undefined) { fields.push("role = ?"); values.push(data.role); }
|
||||||
if (data.image !== undefined) { fields.push("image = ?"); values.push(data.image); }
|
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.instagram !== undefined) { fields.push("instagram = ?"); values.push(data.instagram || null); }
|
||||||
|
if (data.shortDescription !== undefined) { fields.push("short_description = ?"); values.push(data.shortDescription || null); }
|
||||||
if (data.description !== undefined) { fields.push("description = ?"); values.push(data.description || 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.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.victories !== undefined) { fields.push("victories = ?"); values.push(data.victories?.length ? JSON.stringify(data.victories) : null); }
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export interface TeamMember {
|
|||||||
role: string;
|
role: string;
|
||||||
image: string;
|
image: string;
|
||||||
instagram?: string;
|
instagram?: string;
|
||||||
|
shortDescription?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
experience?: string[];
|
experience?: string[];
|
||||||
victories?: VictoryItem[];
|
victories?: VictoryItem[];
|
||||||
|
|||||||
Reference in New Issue
Block a user