mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-12 05:55:02 +10:00
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:
@@ -1,5 +1,11 @@
|
||||
import { match } from "ts-pattern";
|
||||
import type { CustomSectionItem, SectionItem, SectionType } from "@/schema/resume/data";
|
||||
import type {
|
||||
CustomSectionItem,
|
||||
CustomSectionType,
|
||||
SectionItem,
|
||||
SectionType,
|
||||
SummaryItem as SummaryItemType,
|
||||
} from "@/schema/resume/data";
|
||||
import { cn } from "@/utils/style";
|
||||
import { useResumeStore } from "../store/resume";
|
||||
import { AwardsItem } from "./items/awards-item";
|
||||
@@ -13,6 +19,7 @@ import { ProjectsItem } from "./items/projects-item";
|
||||
import { PublicationsItem } from "./items/publications-item";
|
||||
import { ReferencesItem } from "./items/references-item";
|
||||
import { SkillsItem } from "./items/skills-item";
|
||||
import { SummaryItem } from "./items/summary-item";
|
||||
import { VolunteerItem } from "./items/volunteer-item";
|
||||
import { PageSection } from "./page-section";
|
||||
import { PageSummary } from "./page-summary";
|
||||
@@ -23,8 +30,9 @@ type SectionComponentProps = {
|
||||
};
|
||||
|
||||
// Helper to render item component based on type
|
||||
function renderItemByType(type: SectionType, item: CustomSectionItem, itemClassName?: string) {
|
||||
function renderItemByType(type: CustomSectionType, item: CustomSectionItem, itemClassName?: string) {
|
||||
return match(type)
|
||||
.with("summary", () => <SummaryItem {...(item as SummaryItemType)} className={itemClassName} />)
|
||||
.with("profiles", () => <ProfilesItem {...(item as SectionItem<"profiles">)} className={itemClassName} />)
|
||||
.with("experience", () => <ExperienceItem {...(item as SectionItem<"experience">)} className={itemClassName} />)
|
||||
.with("education", () => <EducationItem {...(item as SectionItem<"education">)} className={itemClassName} />)
|
||||
@@ -163,7 +171,9 @@ export function getSectionComponent(
|
||||
|
||||
return (
|
||||
<section className={cn(`page-section page-section-custom page-section-${id}`, sectionClassName)}>
|
||||
<h6 className="mb-1.5 text-(--page-primary-color)">{customSection.title}</h6>
|
||||
{customSection.type !== "summary" && (
|
||||
<h6 className="mb-1.5 text-(--page-primary-color)">{customSection.title}</h6>
|
||||
)}
|
||||
|
||||
<div
|
||||
className="section-content grid gap-x-(--page-gap-x) gap-y-(--page-gap-y)"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { TiptapContent } from "@/components/input/rich-input";
|
||||
import type { SummaryItem as SummaryItemType } from "@/schema/resume/data";
|
||||
import { stripHtml } from "@/utils/string";
|
||||
import { cn } from "@/utils/style";
|
||||
|
||||
type SummaryItemProps = SummaryItemType & {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function SummaryItem({ className, ...item }: SummaryItemProps) {
|
||||
if (!stripHtml(item.content)) return null;
|
||||
|
||||
return (
|
||||
<div className={cn("summary-item", className)}>
|
||||
<TiptapContent content={item.content} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import { CreateProjectDialog, UpdateProjectDialog } from "./resume/sections/proj
|
||||
import { CreatePublicationDialog, UpdatePublicationDialog } from "./resume/sections/publication";
|
||||
import { CreateReferenceDialog, UpdateReferenceDialog } from "./resume/sections/reference";
|
||||
import { CreateSkillDialog, UpdateSkillDialog } from "./resume/sections/skill";
|
||||
import { CreateSummaryItemDialog, UpdateSummaryItemDialog } from "./resume/sections/summary-item";
|
||||
import { CreateVolunteerDialog, UpdateVolunteerDialog } from "./resume/sections/volunteer";
|
||||
import { TemplateGalleryDialog } from "./resume/template/gallery";
|
||||
import { useDialogStore } from "./store";
|
||||
@@ -59,6 +60,8 @@ export function DialogManager() {
|
||||
.with({ type: "resume.sections.volunteer.update" }, ({ data }) => <UpdateVolunteerDialog data={data} />)
|
||||
.with({ type: "resume.sections.references.create" }, ({ data }) => <CreateReferenceDialog data={data} />)
|
||||
.with({ type: "resume.sections.references.update" }, ({ data }) => <UpdateReferenceDialog data={data} />)
|
||||
.with({ type: "resume.sections.summary.create" }, ({ data }) => <CreateSummaryItemDialog data={data} />)
|
||||
.with({ type: "resume.sections.summary.update" }, ({ data }) => <UpdateSummaryItemDialog data={data} />)
|
||||
.with({ type: "resume.sections.custom.create" }, ({ data }) => <CreateCustomSectionDialog data={data} />)
|
||||
.with({ type: "resume.sections.custom.update" }, ({ data }) => <UpdateCustomSectionDialog data={data} />)
|
||||
.otherwise(() => null);
|
||||
|
||||
@@ -11,14 +11,15 @@ import { Input } from "@/components/ui/input";
|
||||
import type { DialogProps } from "@/dialogs/store";
|
||||
import { useDialogStore } from "@/dialogs/store";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { customSectionSchema, type SectionType } from "@/schema/resume/data";
|
||||
import { type CustomSectionType, customSectionSchema } from "@/schema/resume/data";
|
||||
import { generateId } from "@/utils/string";
|
||||
|
||||
const formSchema = customSectionSchema;
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
const SECTION_TYPE_OPTIONS: { value: SectionType; label: string }[] = [
|
||||
const SECTION_TYPE_OPTIONS: { value: CustomSectionType; label: string }[] = [
|
||||
{ value: "summary", label: "Summary" },
|
||||
{ value: "experience", label: "Experience" },
|
||||
{ value: "education", label: "Education" },
|
||||
{ value: "projects", label: "Projects" },
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
import { PencilSimpleLineIcon, PlusIcon } from "@phosphor-icons/react";
|
||||
import { useForm, useFormContext } from "react-hook-form";
|
||||
import type z from "zod";
|
||||
import { RichInput } from "@/components/input/rich-input";
|
||||
import { useResumeStore } from "@/components/resume/store/resume";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import type { DialogProps } from "@/dialogs/store";
|
||||
import { useDialogStore } from "@/dialogs/store";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { summaryItemSchema } from "@/schema/resume/data";
|
||||
import { generateId } from "@/utils/string";
|
||||
|
||||
const formSchema = summaryItemSchema;
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
export function CreateSummaryItemDialog({ data }: DialogProps<"resume.sections.summary.create">) {
|
||||
const closeDialog = useDialogStore((state) => state.closeDialog);
|
||||
const updateResumeData = useResumeStore((state) => state.updateResumeData);
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: generateId(),
|
||||
hidden: data?.item?.hidden ?? false,
|
||||
content: data?.item?.content ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (formData: FormValues) => {
|
||||
updateResumeData((draft) => {
|
||||
if (data?.customSectionId) {
|
||||
const section = draft.customSections.find((s) => s.id === data.customSectionId);
|
||||
if (section) section.items.push(formData);
|
||||
}
|
||||
});
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PlusIcon />
|
||||
<Trans>Create a new summary item</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form className="grid gap-4" onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SummaryItemForm />
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button type="submit" disabled={form.formState.isSubmitting}>
|
||||
<Trans>Create</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
);
|
||||
}
|
||||
|
||||
export function UpdateSummaryItemDialog({ data }: DialogProps<"resume.sections.summary.update">) {
|
||||
const closeDialog = useDialogStore((state) => state.closeDialog);
|
||||
const updateResumeStore = useResumeStore((state) => state.updateResumeData);
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: data.item.id,
|
||||
hidden: data.item.hidden,
|
||||
content: data.item.content,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (formData: FormValues) => {
|
||||
updateResumeStore((draft) => {
|
||||
if (data?.customSectionId) {
|
||||
const section = draft.customSections.find((s) => s.id === data.customSectionId);
|
||||
if (!section) return;
|
||||
const index = section.items.findIndex((item) => item.id === formData.id);
|
||||
if (index !== -1) section.items[index] = formData;
|
||||
}
|
||||
});
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PencilSimpleLineIcon />
|
||||
<Trans>Update an existing summary item</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form className="grid gap-4" onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SummaryItemForm />
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button type="submit" disabled={form.formState.isSubmitting}>
|
||||
<Trans>Save Changes</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
);
|
||||
}
|
||||
|
||||
function SummaryItemForm() {
|
||||
const form = useFormContext<FormValues>();
|
||||
|
||||
return (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="content"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>Content</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<RichInput {...field} value={field.value} onChange={field.onChange} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
publicationItemSchema,
|
||||
referenceItemSchema,
|
||||
skillItemSchema,
|
||||
summaryItemSchema,
|
||||
volunteerItemSchema,
|
||||
} from "@/schema/resume/data";
|
||||
|
||||
@@ -134,6 +135,14 @@ const dialogTypeSchema = z.discriminatedUnion("type", [
|
||||
type: z.literal("resume.sections.references.update"),
|
||||
data: z.object({ item: referenceItemSchema, customSectionId: z.string().optional() }),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("resume.sections.summary.create"),
|
||||
data: z.object({ item: summaryItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("resume.sections.summary.update"),
|
||||
data: z.object({ item: summaryItemSchema, customSectionId: z.string().optional() }),
|
||||
}),
|
||||
z.object({ type: z.literal("resume.sections.custom.create"), data: customSectionSchema.optional() }),
|
||||
z.object({ type: z.literal("resume.sections.custom.update"), data: customSectionSchema }),
|
||||
]);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -86,6 +86,12 @@ export const baseItemSchema = z.object({
|
||||
hidden: z.boolean().describe("Whether to hide the item from the resume."),
|
||||
});
|
||||
|
||||
export const summaryItemSchema = baseItemSchema.extend({
|
||||
content: z.string().describe("The rich text content of the summary item. This should be a HTML-formatted string."),
|
||||
});
|
||||
|
||||
export type SummaryItem = z.infer<typeof summaryItemSchema>;
|
||||
|
||||
export const awardItemSchema = baseItemSchema.extend({
|
||||
title: z.string().min(1).describe("The title of the award."),
|
||||
awarder: z.string().describe("The awarder of the award."),
|
||||
@@ -288,6 +294,7 @@ export type SectionData<T extends SectionType = SectionType> = z.infer<typeof se
|
||||
export type SectionItem<T extends SectionType = SectionType> = SectionData<T>["items"][number];
|
||||
|
||||
export const sectionTypeSchema = z.enum([
|
||||
"summary",
|
||||
"profiles",
|
||||
"experience",
|
||||
"education",
|
||||
@@ -302,7 +309,10 @@ export const sectionTypeSchema = z.enum([
|
||||
"references",
|
||||
]);
|
||||
|
||||
export type CustomSectionType = z.infer<typeof sectionTypeSchema>;
|
||||
|
||||
export const customSectionItemSchema = z.union([
|
||||
summaryItemSchema,
|
||||
profileItemSchema,
|
||||
experienceItemSchema,
|
||||
educationItemSchema,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { WritableDraft } from "immer";
|
||||
import type { CustomSection, ResumeData, SectionItem, SectionType } from "@/schema/resume/data";
|
||||
import type { CustomSection, CustomSectionType, ResumeData, SectionItem, SectionType } from "@/schema/resume/data";
|
||||
import { generateId } from "@/utils/string";
|
||||
import { getSectionTitle as getDefaultSectionTitle } from "./section";
|
||||
|
||||
@@ -62,7 +62,11 @@ function isStandardSectionId(sectionId: string): sectionId is SectionType {
|
||||
* @param customSectionId - The custom section ID (if applicable)
|
||||
* @returns The section title
|
||||
*/
|
||||
export function getSourceSectionTitle(resumeData: ResumeData, type: SectionType, customSectionId?: string): string {
|
||||
export function getSourceSectionTitle(
|
||||
resumeData: ResumeData,
|
||||
type: CustomSectionType,
|
||||
customSectionId?: string,
|
||||
): string {
|
||||
if (customSectionId) {
|
||||
const customSection = resumeData.customSections.find((s) => s.id === customSectionId);
|
||||
return customSection?.title ?? getDefaultSectionTitle(type);
|
||||
@@ -82,7 +86,7 @@ export function getSourceSectionTitle(resumeData: ResumeData, type: SectionType,
|
||||
*/
|
||||
export function getCompatibleMoveTargets(
|
||||
resumeData: ResumeData,
|
||||
sourceType: SectionType,
|
||||
sourceType: CustomSectionType,
|
||||
sourceSectionId: string | undefined,
|
||||
): MoveTargetPage[] {
|
||||
const { pages } = resumeData.metadata.layout;
|
||||
@@ -138,7 +142,7 @@ export function getCompatibleMoveTargets(
|
||||
export function removeItemFromSource(
|
||||
draft: WritableDraft<ResumeData>,
|
||||
itemId: string,
|
||||
type: SectionType,
|
||||
type: CustomSectionType,
|
||||
customSectionId?: string,
|
||||
): SectionItem | null {
|
||||
if (customSectionId) {
|
||||
@@ -152,7 +156,8 @@ export function removeItemFromSource(
|
||||
return removed as SectionItem;
|
||||
}
|
||||
|
||||
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 null;
|
||||
|
||||
const index = section.items.findIndex((item) => item.id === itemId);
|
||||
@@ -174,11 +179,11 @@ export function addItemToSection(
|
||||
draft: WritableDraft<ResumeData>,
|
||||
item: SectionItem,
|
||||
targetSectionId: string,
|
||||
type: SectionType,
|
||||
type: CustomSectionType,
|
||||
): void {
|
||||
// Check if target is a standard section
|
||||
if (isStandardSectionId(targetSectionId) && targetSectionId === type) {
|
||||
const section = draft.sections[type];
|
||||
const section = draft.sections[type as SectionType];
|
||||
if ("items" in section) {
|
||||
section.items.push(item as never);
|
||||
}
|
||||
@@ -205,7 +210,7 @@ export function addItemToSection(
|
||||
export function createCustomSectionWithItem(
|
||||
draft: WritableDraft<ResumeData>,
|
||||
item: SectionItem,
|
||||
type: SectionType,
|
||||
type: CustomSectionType,
|
||||
sectionTitle: string,
|
||||
targetPageIndex: number,
|
||||
): string {
|
||||
@@ -243,7 +248,7 @@ export function createCustomSectionWithItem(
|
||||
export function createPageWithSection(
|
||||
draft: WritableDraft<ResumeData>,
|
||||
item: SectionItem,
|
||||
type: SectionType,
|
||||
type: CustomSectionType,
|
||||
sectionTitle: string,
|
||||
): void {
|
||||
const newSectionId = generateId();
|
||||
|
||||
Reference in New Issue
Block a user