Feature: Implement a new custom section type: summary (#2657)

* feat: add summaryItemSchema for custom summary section type

* feat: add CreateSummaryItemDialog and UpdateSummaryItemDialog

* feat: register summary item dialog types in store

* feat: route summary item dialogs in manager

* feat: add SummaryItem render component

* feat: handle summary type in renderItemByType and hide title for summary sections

* feat: handle summary type in sidebar helpers

* feat: add summary to custom section type options

* fix: update type definitions to support CustomSectionType for summary sections

* chore: extract new i18n strings for summary section

* style: apply biome formatting fixes

* chore: remove TODO.md file containing outdated feature specifications
This commit is contained in:
Amruth Pillai
2026-01-31 01:53:27 +01:00
committed by GitHub
parent 3d1c2d1fb6
commit aa12fcbd36
63 changed files with 1090 additions and 26 deletions
@@ -27,14 +27,25 @@ import {
} from "@/components/ui/dropdown-menu";
import { useDialogStore } from "@/dialogs/store";
import { useConfirm } from "@/hooks/use-confirm";
import type { CustomSection, CustomSectionItem as CustomSectionItemType, SectionType } from "@/schema/resume/data";
import type {
CustomSection,
CustomSectionItem as CustomSectionItemType,
CustomSectionType,
} from "@/schema/resume/data";
import { getSectionTitle } from "@/utils/resume/section";
import { cn } from "@/utils/style";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
function getItemTitle(type: SectionType, item: CustomSectionItemType): string {
function getItemTitle(type: CustomSectionType, item: CustomSectionItemType): string {
return match(type)
.with("summary", () => {
if ("content" in item) {
const stripped = item.content.replace(/<[^>]*>/g, "").trim();
return stripped.length > 50 ? `${stripped.slice(0, 50)}...` : stripped || "Summary";
}
return "Summary";
})
.with("profiles", () => ("network" in item ? item.network : ""))
.with("experience", () => ("company" in item ? item.company : ""))
.with("education", () => ("school" in item ? item.school : ""))
@@ -50,8 +61,9 @@ function getItemTitle(type: SectionType, item: CustomSectionItemType): string {
.exhaustive();
}
function getItemSubtitle(type: SectionType, item: CustomSectionItemType): string | undefined {
function getItemSubtitle(type: CustomSectionType, item: CustomSectionItemType): string | undefined {
return match(type)
.with("summary", () => undefined)
.with("profiles", () => ("username" in item ? item.username : undefined))
.with("experience", () => ("position" in item ? item.position : undefined))
.with("education", () => ("degree" in item ? item.degree : undefined))
@@ -30,7 +30,12 @@ import {
} from "@/components/ui/dropdown-menu";
import { useDialogStore } from "@/dialogs/store";
import { useConfirm } from "@/hooks/use-confirm";
import type { SectionItem as SectionItemType, SectionType } from "@/schema/resume/data";
import type {
CustomSectionItem,
CustomSectionType,
SectionItem as SectionItemType,
SectionType,
} from "@/schema/resume/data";
import {
addItemToSection,
createCustomSectionWithItem,
@@ -46,8 +51,8 @@ import { cn } from "@/utils/style";
// ============================================================================
type MoveItemSubmenuProps = {
type: SectionType;
item: SectionItemType;
type: CustomSectionType;
item: CustomSectionItem | SectionItemType;
customSectionId?: string;
};
@@ -153,15 +158,21 @@ function MoveItemSubmenu({ type, item, customSectionId }: MoveItemSubmenuProps)
// SectionItem Component
// ============================================================================
type Props<T extends SectionItemType> = {
type: SectionType;
type Props<T extends CustomSectionItem | SectionItemType> = {
type: CustomSectionType;
item: T;
title: string;
subtitle?: string;
customSectionId?: string;
};
export function SectionItem<T extends SectionItemType>({ type, item, title, subtitle, customSectionId }: Props<T>) {
export function SectionItem<T extends CustomSectionItem | SectionItemType>({
type,
item,
title,
subtitle,
customSectionId,
}: Props<T>) {
const confirm = useConfirm();
const controls = useDragControls();
const { openDialog } = useDialogStore();
@@ -176,7 +187,8 @@ export function SectionItem<T extends SectionItemType>({ type, item, title, subt
if (index === -1) return;
section.items[index].hidden = !section.items[index].hidden;
} else {
const section = draft.sections[type];
// Type assertion: when customSectionId is not provided, type is always a built-in SectionType
const section = draft.sections[type as SectionType];
if (!("items" in section)) return;
const index = section.items.findIndex((_item) => _item.id === item.id);
if (index === -1) return;
@@ -211,7 +223,8 @@ export function SectionItem<T extends SectionItemType>({ type, item, title, subt
if (index === -1) return;
section.items.splice(index, 1);
} else {
const section = draft.sections[type];
// Type assertion: when customSectionId is not provided, type is always a built-in SectionType
const section = draft.sections[type as SectionType];
if (!("items" in section)) return;
const index = section.items.findIndex((_item) => _item.id === item.id);
if (index === -1) return;
@@ -298,7 +311,7 @@ export function SectionItem<T extends SectionItemType>({ type, item, title, subt
}
type AddButtonProps = Omit<ButtonProps, "type"> & {
type: SectionType | "custom";
type: CustomSectionType | "custom";
customSectionId?: string;
};