mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-26 18:04:45 +10:00
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type { SectionItem } from "@/schema/resume/data";
|
||||
|
||||
import { TiptapContent } from "@/components/input/rich-input";
|
||||
import { filterFieldValues } from "@/utils/field";
|
||||
import { stripHtml } from "@/utils/string";
|
||||
import { cn } from "@/utils/style";
|
||||
|
||||
@@ -12,24 +13,44 @@ type AwardsItemProps = SectionItem<"awards"> & {
|
||||
};
|
||||
|
||||
export function AwardsItem({ className, ...item }: AwardsItemProps) {
|
||||
const headerValues = {
|
||||
title: item.title,
|
||||
date: item.date,
|
||||
awarder: item.awarder,
|
||||
};
|
||||
const headerFields = filterFieldValues(
|
||||
headerValues,
|
||||
{
|
||||
key: "title",
|
||||
content: (
|
||||
<LinkedTitle
|
||||
title={item.title}
|
||||
website={item.website}
|
||||
showLinkInTitle={item.options?.showLinkInTitle}
|
||||
className="section-item-title awards-item-title"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "date",
|
||||
content: <span className="section-item-metadata awards-item-date">{item.date}</span>,
|
||||
},
|
||||
{
|
||||
key: "awarder",
|
||||
content: <span className="section-item-metadata awards-item-awarder">{item.awarder}</span>,
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cn("awards-item", className)}>
|
||||
{/* Header */}
|
||||
<div className="section-item-header awards-item-header">
|
||||
{/* Row 1 */}
|
||||
<div className="flex items-start justify-between gap-x-2">
|
||||
<LinkedTitle
|
||||
title={item.title}
|
||||
website={item.website}
|
||||
showLinkInTitle={item.options?.showLinkInTitle}
|
||||
className="section-item-title awards-item-title"
|
||||
/>
|
||||
<span className="section-item-metadata awards-item-date shrink-0 text-end">{item.date}</span>
|
||||
</div>
|
||||
|
||||
{/* Row 2 */}
|
||||
<div className="flex items-start justify-between gap-x-2">
|
||||
<span className="section-item-metadata awards-item-awarder">{item.awarder}</span>
|
||||
<div className="grid grid-cols-2 items-start gap-x-2">
|
||||
{headerFields.map((field, index) => (
|
||||
<div key={field.key} className={cn(index % 2 === 1 && "shrink-0 justify-self-end text-end")}>
|
||||
{field.content}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { SectionItem } from "@/schema/resume/data";
|
||||
|
||||
import { TiptapContent } from "@/components/input/rich-input";
|
||||
import { filterFieldValues } from "@/utils/field";
|
||||
import { stripHtml } from "@/utils/string";
|
||||
import { cn } from "@/utils/style";
|
||||
|
||||
@@ -12,24 +13,44 @@ type CertificationsItemProps = SectionItem<"certifications"> & {
|
||||
};
|
||||
|
||||
export function CertificationsItem({ className, ...item }: CertificationsItemProps) {
|
||||
const headerValues = {
|
||||
title: item.title,
|
||||
date: item.date,
|
||||
issuer: item.issuer,
|
||||
};
|
||||
const headerFields = filterFieldValues(
|
||||
headerValues,
|
||||
{
|
||||
key: "title",
|
||||
content: (
|
||||
<LinkedTitle
|
||||
title={item.title}
|
||||
website={item.website}
|
||||
showLinkInTitle={item.options?.showLinkInTitle}
|
||||
className="section-item-title certifications-item-title"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "date",
|
||||
content: <span className="section-item-metadata certifications-item-date">{item.date}</span>,
|
||||
},
|
||||
{
|
||||
key: "issuer",
|
||||
content: <span className="section-item-metadata certifications-item-issuer">{item.issuer}</span>,
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cn("certifications-item", className)}>
|
||||
{/* Header */}
|
||||
<div className="section-item-header certifications-item-header">
|
||||
{/* Row 1 */}
|
||||
<div className="flex items-start justify-between gap-x-2">
|
||||
<LinkedTitle
|
||||
title={item.title}
|
||||
website={item.website}
|
||||
showLinkInTitle={item.options?.showLinkInTitle}
|
||||
className="section-item-title certifications-item-title"
|
||||
/>
|
||||
<span className="section-item-metadata certifications-item-date shrink-0 text-end">{item.date}</span>
|
||||
</div>
|
||||
|
||||
{/* Row 2 */}
|
||||
<div className="flex items-start justify-between gap-x-2">
|
||||
<span className="section-item-metadata certifications-item-issuer">{item.issuer}</span>
|
||||
<div className="grid grid-cols-2 items-start gap-x-2">
|
||||
{headerFields.map((field, index) => (
|
||||
<div key={field.key} className={cn(index % 2 === 1 && "shrink-0 justify-self-end text-end")}>
|
||||
{field.content}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { SectionItem } from "@/schema/resume/data";
|
||||
|
||||
import { TiptapContent } from "@/components/input/rich-input";
|
||||
import { filterFieldValues } from "@/utils/field";
|
||||
import { stripHtml } from "@/utils/string";
|
||||
import { cn } from "@/utils/style";
|
||||
|
||||
@@ -12,29 +13,51 @@ type EducationItemProps = SectionItem<"education"> & {
|
||||
};
|
||||
|
||||
export function EducationItem({ className, ...item }: EducationItemProps) {
|
||||
const degreeAndGrade = [item.degree, item.grade].filter(Boolean).join(" • ");
|
||||
const locationAndPeriod = [item.location, item.period].filter(Boolean).join(" • ");
|
||||
const headerValues = {
|
||||
school: item.school,
|
||||
degreeAndGrade,
|
||||
area: item.area,
|
||||
locationAndPeriod,
|
||||
};
|
||||
const headerFields = filterFieldValues(
|
||||
headerValues,
|
||||
{
|
||||
key: "school",
|
||||
content: (
|
||||
<LinkedTitle
|
||||
title={item.school}
|
||||
website={item.website}
|
||||
showLinkInTitle={item.options?.showLinkInTitle}
|
||||
className="section-item-title education-item-title"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "degreeAndGrade",
|
||||
content: <span className="section-item-metadata education-item-degree-grade">{degreeAndGrade}</span>,
|
||||
},
|
||||
{
|
||||
key: "area",
|
||||
content: <span className="section-item-metadata education-item-area">{item.area}</span>,
|
||||
},
|
||||
{
|
||||
key: "locationAndPeriod",
|
||||
content: <span className="section-item-metadata education-item-location-period">{locationAndPeriod}</span>,
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cn("education-item", className)}>
|
||||
{/* Header */}
|
||||
<div className="section-item-header education-item-header mb-2">
|
||||
{/* Row 1 */}
|
||||
<div className="flex items-start justify-between gap-x-2">
|
||||
<LinkedTitle
|
||||
title={item.school}
|
||||
website={item.website}
|
||||
showLinkInTitle={item.options?.showLinkInTitle}
|
||||
className="section-item-title education-item-title"
|
||||
/>
|
||||
<span className="section-item-metadata education-item-degree-grade shrink-0 text-end">
|
||||
{[item.degree, item.grade].filter(Boolean).join(" • ")}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Row 2 */}
|
||||
<div className="flex items-start justify-between gap-x-2">
|
||||
<span className="section-item-metadata education-item-area">{item.area}</span>
|
||||
<span className="section-item-metadata education-item-location-period shrink-0 text-end">
|
||||
{[item.location, item.period].filter(Boolean).join(" • ")}
|
||||
</span>
|
||||
<div className="grid grid-cols-2 items-start gap-x-2">
|
||||
{headerFields.map((field, index) => (
|
||||
<div key={field.key} className={cn(index % 2 === 1 && "shrink-0 justify-self-end text-end")}>
|
||||
{field.content}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { SectionItem } from "@/schema/resume/data";
|
||||
|
||||
import { TiptapContent } from "@/components/input/rich-input";
|
||||
import { filterFieldValues } from "@/utils/field";
|
||||
import { stripHtml } from "@/utils/string";
|
||||
import { cn } from "@/utils/style";
|
||||
|
||||
@@ -13,36 +14,50 @@ type ExperienceItemProps = SectionItem<"experience"> & {
|
||||
|
||||
export function ExperienceItem({ className, ...item }: ExperienceItemProps) {
|
||||
const hasRoles = Array.isArray(item.roles) && item.roles.length > 0;
|
||||
const headerValues = {
|
||||
company: item.company,
|
||||
location: item.location,
|
||||
position: item.position,
|
||||
period: item.period,
|
||||
};
|
||||
const headerFields = filterFieldValues(
|
||||
headerValues,
|
||||
{
|
||||
key: "company",
|
||||
content: (
|
||||
<LinkedTitle
|
||||
title={item.company}
|
||||
website={item.website}
|
||||
showLinkInTitle={item.options?.showLinkInTitle}
|
||||
className="section-item-title experience-item-title"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "location",
|
||||
content: <span className="section-item-metadata experience-item-location">{item.location}</span>,
|
||||
},
|
||||
{
|
||||
key: "position",
|
||||
content: <span className="section-item-metadata experience-item-position">{item.position}</span>,
|
||||
},
|
||||
{
|
||||
key: "period",
|
||||
content: <span className="section-item-metadata experience-item-period">{item.period}</span>,
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cn("experience-item", className)}>
|
||||
{/* Header */}
|
||||
<div className="section-item-header experience-item-header">
|
||||
{/* Row 1: Company + Location */}
|
||||
<div className="flex items-start justify-between gap-x-2">
|
||||
<LinkedTitle
|
||||
title={item.company}
|
||||
website={item.website}
|
||||
showLinkInTitle={item.options?.showLinkInTitle}
|
||||
className="section-item-title experience-item-title"
|
||||
/>
|
||||
<span className="section-item-metadata experience-item-location shrink-0 text-end">{item.location}</span>
|
||||
<div className="grid grid-cols-2 items-start gap-x-2">
|
||||
{headerFields.map((field, index) => (
|
||||
<div key={field.key} className={cn(index % 2 === 1 && "shrink-0 justify-self-end text-end")}>
|
||||
{field.content}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Row 2: Position + Period */}
|
||||
{(!hasRoles || item.position) && (
|
||||
<div className="flex items-start justify-between gap-x-2">
|
||||
<span className="section-item-metadata experience-item-position">{item.position}</span>
|
||||
<span className="section-item-metadata experience-item-period shrink-0 text-end">{item.period}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Overall period when hasRoles and no summary position */}
|
||||
{hasRoles && !item.position && item.period && (
|
||||
<div className="flex items-start justify-end gap-x-2">
|
||||
<span className="section-item-metadata experience-item-period shrink-0 text-end">{item.period}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Role Progression */}
|
||||
@@ -52,7 +67,7 @@ export function ExperienceItem({ className, ...item }: ExperienceItemProps) {
|
||||
<div key={role.id} className="experience-item-role">
|
||||
<div className="flex items-start justify-between gap-x-2">
|
||||
<span className="section-item-metadata experience-item-role-position">{role.position}</span>
|
||||
<span className="section-item-metadata experience-item-role-period shrink-0 text-end">
|
||||
<span className="section-item-metadata experience-item-role-period shrink-0 justify-self-end text-end">
|
||||
{role.period}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { SectionItem } from "@/schema/resume/data";
|
||||
|
||||
import { TiptapContent } from "@/components/input/rich-input";
|
||||
import { filterFieldValues } from "@/utils/field";
|
||||
import { stripHtml } from "@/utils/string";
|
||||
import { cn } from "@/utils/style";
|
||||
|
||||
@@ -12,24 +13,44 @@ type PublicationsItemProps = SectionItem<"publications"> & {
|
||||
};
|
||||
|
||||
export function PublicationsItem({ className, ...item }: PublicationsItemProps) {
|
||||
const headerValues = {
|
||||
title: item.title,
|
||||
date: item.date,
|
||||
publisher: item.publisher,
|
||||
};
|
||||
const headerFields = filterFieldValues(
|
||||
headerValues,
|
||||
{
|
||||
key: "title",
|
||||
content: (
|
||||
<LinkedTitle
|
||||
title={item.title}
|
||||
website={item.website}
|
||||
showLinkInTitle={item.options?.showLinkInTitle}
|
||||
className="section-item-title publications-item-title"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "date",
|
||||
content: <span className="section-item-metadata publications-item-date">{item.date}</span>,
|
||||
},
|
||||
{
|
||||
key: "publisher",
|
||||
content: <span className="section-item-metadata publications-item-publisher">{item.publisher}</span>,
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cn("publications-item", className)}>
|
||||
{/* Header */}
|
||||
<div className="section-item-header publications-item-header">
|
||||
{/* Row 1 */}
|
||||
<div className="flex items-start justify-between gap-x-2">
|
||||
<LinkedTitle
|
||||
title={item.title}
|
||||
website={item.website}
|
||||
showLinkInTitle={item.options?.showLinkInTitle}
|
||||
className="section-item-title publications-item-title"
|
||||
/>
|
||||
<span className="section-item-metadata publications-item-date shrink-0 text-end">{item.date}</span>
|
||||
</div>
|
||||
|
||||
{/* Row 2 */}
|
||||
<div className="flex items-start justify-between gap-x-2">
|
||||
<span className="section-item-metadata publications-item-publisher">{item.publisher}</span>
|
||||
<div className="grid grid-cols-2 items-start gap-x-2">
|
||||
{headerFields.map((field, index) => (
|
||||
<div key={field.key} className={cn(index % 2 === 1 && "shrink-0 justify-self-end text-end")}>
|
||||
{field.content}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { SectionItem } from "@/schema/resume/data";
|
||||
|
||||
import { TiptapContent } from "@/components/input/rich-input";
|
||||
import { filterFieldValues } from "@/utils/field";
|
||||
import { stripHtml } from "@/utils/string";
|
||||
import { cn } from "@/utils/style";
|
||||
|
||||
@@ -12,23 +13,39 @@ type ReferencesItemProps = SectionItem<"references"> & {
|
||||
};
|
||||
|
||||
export function ReferencesItem({ className, ...item }: ReferencesItemProps) {
|
||||
const headerValues = {
|
||||
name: item.name,
|
||||
position: item.position,
|
||||
};
|
||||
const headerFields = filterFieldValues(
|
||||
headerValues,
|
||||
{
|
||||
key: "name",
|
||||
content: (
|
||||
<LinkedTitle
|
||||
title={item.name}
|
||||
website={item.website}
|
||||
showLinkInTitle={item.options?.showLinkInTitle}
|
||||
className="section-item-title references-item-name"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "position",
|
||||
content: <span className="section-item-metadata references-item-position">{item.position}</span>,
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cn("references-item", className)}>
|
||||
{/* Header */}
|
||||
<div className="section-item-header references-item-header">
|
||||
{/* Row 1 */}
|
||||
<div className="flex items-start justify-between gap-x-2">
|
||||
<LinkedTitle
|
||||
title={item.name}
|
||||
website={item.website}
|
||||
showLinkInTitle={item.options?.showLinkInTitle}
|
||||
className="section-item-title references-item-name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Row 2 */}
|
||||
<div className="flex items-start justify-between gap-x-2">
|
||||
<span className="section-item-metadata references-item-position">{item.position}</span>
|
||||
<div className="grid grid-cols-2 items-start gap-x-2">
|
||||
{headerFields.map((field, index) => (
|
||||
<div key={field.key} className={cn(index % 2 === 1 && "shrink-0 justify-self-end text-end")}>
|
||||
{field.content}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { SectionItem } from "@/schema/resume/data";
|
||||
|
||||
import { TiptapContent } from "@/components/input/rich-input";
|
||||
import { filterFieldValues } from "@/utils/field";
|
||||
import { stripHtml } from "@/utils/string";
|
||||
import { cn } from "@/utils/style";
|
||||
|
||||
@@ -12,24 +13,44 @@ type VolunteerItemProps = SectionItem<"volunteer"> & {
|
||||
};
|
||||
|
||||
export function VolunteerItem({ className, ...item }: VolunteerItemProps) {
|
||||
const headerValues = {
|
||||
organization: item.organization,
|
||||
period: item.period,
|
||||
location: item.location,
|
||||
};
|
||||
const headerFields = filterFieldValues(
|
||||
headerValues,
|
||||
{
|
||||
key: "organization",
|
||||
content: (
|
||||
<LinkedTitle
|
||||
title={item.organization}
|
||||
website={item.website}
|
||||
showLinkInTitle={item.options?.showLinkInTitle}
|
||||
className="section-item-title volunteer-item-title"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "period",
|
||||
content: <span className="section-item-metadata volunteer-item-period">{item.period}</span>,
|
||||
},
|
||||
{
|
||||
key: "location",
|
||||
content: <span className="section-item-metadata volunteer-item-location">{item.location}</span>,
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cn("volunteer-item", className)}>
|
||||
{/* Header */}
|
||||
<div className="section-item-header volunteer-item-header">
|
||||
{/* Row 1 */}
|
||||
<div className="flex items-start justify-between gap-x-2">
|
||||
<LinkedTitle
|
||||
title={item.organization}
|
||||
website={item.website}
|
||||
showLinkInTitle={item.options?.showLinkInTitle}
|
||||
className="section-item-title volunteer-item-title"
|
||||
/>
|
||||
<span className="section-item-metadata volunteer-item-period shrink-0 text-end">{item.period}</span>
|
||||
</div>
|
||||
|
||||
{/* Row 2 */}
|
||||
<div className="flex items-start justify-between gap-x-2">
|
||||
<span className="section-item-metadata volunteer-item-location">{item.location}</span>
|
||||
<div className="grid grid-cols-2 items-start gap-x-2">
|
||||
{headerFields.map((field, index) => (
|
||||
<div key={field.key} className={cn(index % 2 === 1 && "shrink-0 justify-self-end text-end")}>
|
||||
{field.content}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user