mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-13 16:22:59 +10:00
feat: additional work sections
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@ -10,4 +10,7 @@ node_modules
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# Turbo
|
# Turbo
|
||||||
.turbo
|
.turbo
|
||||||
|
|
||||||
|
# Intellij
|
||||||
|
.idea
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
import { Add, Star } from '@mui/icons-material';
|
import { Add, Star } from '@mui/icons-material';
|
||||||
import { Button, Divider, IconButton, SwipeableDrawer, Tooltip, useMediaQuery, useTheme } from '@mui/material';
|
import { Button, Divider, IconButton, SwipeableDrawer, Tooltip, useMediaQuery, useTheme } from '@mui/material';
|
||||||
import { Section as SectionRecord } from '@reactive-resume/schema';
|
import { Section as SectionRecord } from '@reactive-resume/schema';
|
||||||
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useMemo } from 'react';
|
import React, { ReactComponentElement, useMemo } from 'react';
|
||||||
import { validate } from 'uuid';
|
import { validate } from 'uuid';
|
||||||
|
|
||||||
import Logo from '@/components/shared/Logo';
|
import Logo from '@/components/shared/Logo';
|
||||||
import { getCustomSections, left } from '@/config/sections';
|
import { getCustomSections, getSectionsByType, left } from '@/config/sections';
|
||||||
import { setSidebarState } from '@/store/build/buildSlice';
|
import { setSidebarState } from '@/store/build/buildSlice';
|
||||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||||
import { addSection } from '@/store/resume/resumeSlice';
|
import { addSection } from '@/store/resume/resumeSlice';
|
||||||
@ -52,7 +53,49 @@ const LeftSidebar = () => {
|
|||||||
items: [],
|
items: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch(addSection({ value: newSection }));
|
dispatch(addSection({ value: newSection, type: 'custom' }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const sectionsList = () => {
|
||||||
|
const sectionsComponents: Array<ReactComponentElement<any>> = [];
|
||||||
|
|
||||||
|
for (const item of left) {
|
||||||
|
const id = (item as any).id;
|
||||||
|
const component = (item as any).component;
|
||||||
|
const type = component.props.type || 'basic';
|
||||||
|
const addMore = !!component.props.addMore;
|
||||||
|
|
||||||
|
sectionsComponents.push(
|
||||||
|
<section key={id} id={id}>
|
||||||
|
{component}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (addMore) {
|
||||||
|
const additionalSections = getSectionsByType(sections, type);
|
||||||
|
const elements = [];
|
||||||
|
for (const element of additionalSections) {
|
||||||
|
const newId = element.id;
|
||||||
|
|
||||||
|
const props = cloneDeep(component.props);
|
||||||
|
props.path = 'sections.' + newId;
|
||||||
|
props.name = element.name;
|
||||||
|
props.isDeletable = true;
|
||||||
|
props.addMore = false;
|
||||||
|
props.isDuplicated = true;
|
||||||
|
const newComponent = React.cloneElement(component, props);
|
||||||
|
|
||||||
|
elements.push(
|
||||||
|
<section key={newId} id={`section-${newId}`}>
|
||||||
|
{newComponent}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
sectionsComponents.push(...elements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sectionsComponents;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -100,12 +143,7 @@ const LeftSidebar = () => {
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
{left.map(({ id, component }) => (
|
{sectionsList()}
|
||||||
<section key={id} id={id}>
|
|
||||||
{component}
|
|
||||||
</section>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{customSections.map(({ id }) => (
|
{customSections.map(({ id }) => (
|
||||||
<section key={id} id={`section-${id}`}>
|
<section key={id} id={`section-${id}`}>
|
||||||
<Section path={`sections.${id}`} isEditable isHideable isDeletable />
|
<Section path={`sections.${id}`} isEditable isHideable isDeletable />
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Add } from '@mui/icons-material';
|
import { Add } from '@mui/icons-material';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
import { ListItem } from '@reactive-resume/schema';
|
import { ListItem, Section as SectionRecord, SectionType } from '@reactive-resume/schema';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
@ -10,28 +10,34 @@ import Heading from '@/components/shared/Heading';
|
|||||||
import List from '@/components/shared/List';
|
import List from '@/components/shared/List';
|
||||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||||
import { ModalName, setModalState } from '@/store/modal/modalSlice';
|
import { ModalName, setModalState } from '@/store/modal/modalSlice';
|
||||||
import { duplicateItem } from '@/store/resume/resumeSlice';
|
import { duplicateItem, duplicateSection } from '@/store/resume/resumeSlice';
|
||||||
|
|
||||||
import SectionSettings from './SectionSettings';
|
import SectionSettings from './SectionSettings';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
path: `sections.${string}`;
|
path: `sections.${string}`;
|
||||||
|
type?: SectionType;
|
||||||
name?: string;
|
name?: string;
|
||||||
titleKey?: string;
|
titleKey?: string;
|
||||||
subtitleKey?: string;
|
subtitleKey?: string;
|
||||||
isEditable?: boolean;
|
isEditable?: boolean;
|
||||||
isHideable?: boolean;
|
isHideable?: boolean;
|
||||||
isDeletable?: boolean;
|
isDeletable?: boolean;
|
||||||
|
addMore?: boolean;
|
||||||
|
isDuplicated?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Section: React.FC<Props> = ({
|
const Section: React.FC<Props> = ({
|
||||||
path,
|
path,
|
||||||
name = 'Section Name',
|
name = 'Section Name',
|
||||||
|
type = 'basic',
|
||||||
titleKey = 'title',
|
titleKey = 'title',
|
||||||
subtitleKey = 'subtitle',
|
subtitleKey = 'subtitle',
|
||||||
isEditable = false,
|
isEditable = false,
|
||||||
isHideable = false,
|
isHideable = false,
|
||||||
isDeletable = false,
|
isDeletable = false,
|
||||||
|
addMore = false,
|
||||||
|
isDuplicated = false,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -42,21 +48,43 @@ const Section: React.FC<Props> = ({
|
|||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
const id = path.split('.')[1];
|
const id = path.split('.')[1];
|
||||||
const modal: ModalName = validate(id) ? 'builder.sections.custom' : `builder.${path}`;
|
let modal: ModalName = validate(id) ? 'builder.sections.custom' : `builder.${path}`;
|
||||||
|
|
||||||
|
if (type) {
|
||||||
|
modal = `builder.sections.${type}`;
|
||||||
|
}
|
||||||
dispatch(setModalState({ modal, state: { open: true, payload: { path } } }));
|
dispatch(setModalState({ modal, state: { open: true, payload: { path } } }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (item: ListItem) => {
|
const handleEdit = (item: ListItem) => {
|
||||||
const id = path.split('.')[1];
|
const id = path.split('.')[1];
|
||||||
const modal: ModalName = validate(id) ? 'builder.sections.custom' : `builder.${path}`;
|
let modal: ModalName = validate(id) ? 'builder.sections.custom' : `builder.${path}`;
|
||||||
|
|
||||||
const payload = validate(id) ? { path, item } : { item };
|
const payload = validate(id) ? { path, item } : { item };
|
||||||
|
|
||||||
|
if (isDuplicated) {
|
||||||
|
modal = `builder.sections.${type}`;
|
||||||
|
payload.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(setModalState({ modal, state: { open: true, payload } }));
|
dispatch(setModalState({ modal, state: { open: true, payload } }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDuplicate = (item: ListItem) => dispatch(duplicateItem({ path: `${path}.items`, value: item }));
|
const handleDuplicate = (item: ListItem) => dispatch(duplicateItem({ path: `${path}.items`, value: item }));
|
||||||
|
|
||||||
|
const handleDuplicateSection = () => {
|
||||||
|
const newSection: SectionRecord = {
|
||||||
|
name: `${heading}`,
|
||||||
|
type: type,
|
||||||
|
visible: true,
|
||||||
|
columns: 2,
|
||||||
|
items: [],
|
||||||
|
isDuplicated: true
|
||||||
|
};
|
||||||
|
|
||||||
|
dispatch(duplicateSection({ value: newSection, type }));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Heading path={path} name={name} isEditable={isEditable} isHideable={isHideable} isDeletable={isDeletable} />
|
<Heading path={path} name={name} isEditable={isEditable} isHideable={isHideable} isDeletable={isDeletable} />
|
||||||
@ -77,6 +105,16 @@ const Section: React.FC<Props> = ({
|
|||||||
{t<string>('builder.common.actions.add', { token: heading })}
|
{t<string>('builder.common.actions.add', { token: heading })}
|
||||||
</Button>
|
</Button>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
{addMore ? (
|
||||||
|
<div className="py-6 text-right">
|
||||||
|
<Button fullWidth variant="outlined" startIcon={<Add />} onClick={handleDuplicateSection}>
|
||||||
|
{t<string>('builder.common.actions.duplicate')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import {
|
|||||||
VolunteerActivism,
|
VolunteerActivism,
|
||||||
Work,
|
Work,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { Section as SectionRecord } from '@reactive-resume/schema';
|
import { Section as SectionRecord, SectionType } from '@reactive-resume/schema';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
|
||||||
import Basics from '@/components/build/LeftSidebar/sections/Basics';
|
import Basics from '@/components/build/LeftSidebar/sections/Basics';
|
||||||
@ -60,59 +60,69 @@ export const left: SidebarSection[] = [
|
|||||||
{
|
{
|
||||||
id: 'work',
|
id: 'work',
|
||||||
icon: <Work />,
|
icon: <Work />,
|
||||||
component: <Section path="sections.work" titleKey="name" subtitleKey="position" isEditable isHideable />,
|
component: (
|
||||||
|
<Section
|
||||||
|
type={'work'}
|
||||||
|
addMore={true}
|
||||||
|
path="sections.work"
|
||||||
|
titleKey="name"
|
||||||
|
subtitleKey="position"
|
||||||
|
isEditable
|
||||||
|
isHideable
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'education',
|
id: 'education',
|
||||||
icon: <School />,
|
icon: <School />,
|
||||||
component: <Section path="sections.education" titleKey="institution" subtitleKey="area" isEditable isHideable />,
|
component: <Section type={"education"} path="sections.education" titleKey="institution" subtitleKey="area" isEditable isHideable />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'awards',
|
id: 'awards',
|
||||||
icon: <EmojiEvents />,
|
icon: <EmojiEvents />,
|
||||||
component: <Section path="sections.awards" titleKey="title" subtitleKey="awarder" isEditable isHideable />,
|
component: <Section type={"awards"} path="sections.awards" titleKey="title" subtitleKey="awarder" isEditable isHideable />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'certifications',
|
id: 'certifications',
|
||||||
icon: <CardGiftcard />,
|
icon: <CardGiftcard />,
|
||||||
component: <Section path="sections.certifications" titleKey="name" subtitleKey="issuer" isEditable isHideable />,
|
component: <Section type={"certifications"} path="sections.certifications" titleKey="name" subtitleKey="issuer" isEditable isHideable />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'publications',
|
id: 'publications',
|
||||||
icon: <MenuBook />,
|
icon: <MenuBook />,
|
||||||
component: <Section path="sections.publications" titleKey="name" subtitleKey="publisher" isEditable isHideable />,
|
component: <Section type={"publications"} path="sections.publications" titleKey="name" subtitleKey="publisher" isEditable isHideable />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'skills',
|
id: 'skills',
|
||||||
icon: <Architecture />,
|
icon: <Architecture />,
|
||||||
component: <Section path="sections.skills" titleKey="name" subtitleKey="level" isEditable isHideable />,
|
component: <Section type={"skills"} path="sections.skills" titleKey="name" subtitleKey="level" isEditable isHideable />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'languages',
|
id: 'languages',
|
||||||
icon: <Language />,
|
icon: <Language />,
|
||||||
component: <Section path="sections.languages" titleKey="name" subtitleKey="level" isEditable isHideable />,
|
component: <Section type={"languages"} path="sections.languages" titleKey="name" subtitleKey="level" isEditable isHideable />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'interests',
|
id: 'interests',
|
||||||
icon: <Sailing />,
|
icon: <Sailing />,
|
||||||
component: <Section path="sections.interests" titleKey="name" subtitleKey="keywords" isEditable isHideable />,
|
component: <Section type={"interests"} path="sections.interests" titleKey="name" subtitleKey="keywords" isEditable isHideable />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'volunteer',
|
id: 'volunteer',
|
||||||
icon: <VolunteerActivism />,
|
icon: <VolunteerActivism />,
|
||||||
component: (
|
component: (
|
||||||
<Section path="sections.volunteer" titleKey="organization" subtitleKey="position" isEditable isHideable />
|
<Section type={"volunteer"} path="sections.volunteer" titleKey="organization" subtitleKey="position" isEditable isHideable />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'projects',
|
id: 'projects',
|
||||||
icon: <Coffee />,
|
icon: <Coffee />,
|
||||||
component: <Section path="sections.projects" titleKey="name" subtitleKey="description" isEditable isHideable />,
|
component: <Section type={"projects"} path="sections.projects" titleKey="name" subtitleKey="description" isEditable isHideable />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'references',
|
id: 'references',
|
||||||
icon: <Groups />,
|
icon: <Groups />,
|
||||||
component: <Section path="sections.references" titleKey="name" subtitleKey="relationship" isEditable isHideable />,
|
component: <Section type={"references"} path="sections.references" titleKey="name" subtitleKey="relationship" isEditable isHideable />,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -164,6 +174,21 @@ export const right: SidebarSection[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const getSectionsByType = (
|
||||||
|
sections: Record<string, SectionRecord>,
|
||||||
|
type: SectionType
|
||||||
|
): Array<Required<SectionRecord>> => {
|
||||||
|
if (isEmpty(sections)) return [];
|
||||||
|
|
||||||
|
return Object.entries(sections).reduce((acc, [id, section]) => {
|
||||||
|
if (section.type.startsWith(type) && section.isDuplicated) {
|
||||||
|
return [...acc, { ...section, id }];
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, [] as Array<Required<SectionRecord>>);
|
||||||
|
};
|
||||||
|
|
||||||
export const getCustomSections = (sections: Record<string, SectionRecord>): Array<Required<SectionRecord>> => {
|
export const getCustomSections = (sections: Record<string, SectionRecord>): Array<Required<SectionRecord>> => {
|
||||||
if (isEmpty(sections)) return [];
|
if (isEmpty(sections)) return [];
|
||||||
|
|
||||||
|
|||||||
@ -169,7 +169,8 @@ const LoginModal: React.FC = () => {
|
|||||||
|
|
||||||
<p className="text-xs">
|
<p className="text-xs">
|
||||||
<Trans t={t} i18nKey="modals.auth.login.recover-text">
|
<Trans t={t} i18nKey="modals.auth.login.recover-text">
|
||||||
In case you have forgotten your password, you can <a onClick={handleRecoverAccount}>recover your account here.</a>
|
In case you have forgotten your password, you can{' '}
|
||||||
|
<a onClick={handleRecoverAccount}>recover your account here.</a>
|
||||||
</Trans>
|
</Trans>
|
||||||
</p>
|
</p>
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { joiResolver } from '@hookform/resolvers/joi';
|
|||||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||||
import { Button, TextField } from '@mui/material';
|
import { Button, TextField } from '@mui/material';
|
||||||
import { DatePicker } from '@mui/x-date-pickers';
|
import { DatePicker } from '@mui/x-date-pickers';
|
||||||
import { SectionPath, WorkExperience } from '@reactive-resume/schema';
|
import { WorkExperience } from '@reactive-resume/schema';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
@ -20,8 +20,6 @@ import { addItem, editItem } from '@/store/resume/resumeSlice';
|
|||||||
|
|
||||||
type FormData = WorkExperience;
|
type FormData = WorkExperience;
|
||||||
|
|
||||||
const path: SectionPath = 'sections.work';
|
|
||||||
|
|
||||||
const defaultState: FormData = {
|
const defaultState: FormData = {
|
||||||
name: '',
|
name: '',
|
||||||
position: '',
|
position: '',
|
||||||
@ -51,9 +49,11 @@ const WorkModal: React.FC = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
|
||||||
|
|
||||||
|
const { open: isOpen, payload } = useAppSelector((state) => state.modal['builder.sections.work']);
|
||||||
|
const path: string = get(payload, 'path', 'sections.work');
|
||||||
const item: FormData = get(payload, 'item', null);
|
const item: FormData = get(payload, 'item', null);
|
||||||
|
|
||||||
const isEditMode = useMemo(() => !!item, [item]);
|
const isEditMode = useMemo(() => !!item, [item]);
|
||||||
|
|
||||||
const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
|
const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||||
@ -77,7 +77,7 @@ const WorkModal: React.FC = () => {
|
|||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
dispatch(
|
dispatch(
|
||||||
setModalState({
|
setModalState({
|
||||||
modal: `builder.${path}`,
|
modal: 'builder.sections.work',
|
||||||
state: { open: false },
|
state: { open: false },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,7 +3,8 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"add": "Add New {{token}}",
|
"add": "Add New {{token}}",
|
||||||
"delete": "Delete {{token}}",
|
"delete": "Delete {{token}}",
|
||||||
"edit": "Edit {{token}}"
|
"edit": "Edit {{token}}",
|
||||||
|
"duplicate": "Duplicate Section"
|
||||||
},
|
},
|
||||||
"columns": {
|
"columns": {
|
||||||
"heading": "Columns",
|
"heading": "Columns",
|
||||||
|
|||||||
@ -9,6 +9,7 @@ export type ModalName =
|
|||||||
| 'dashboard.import-external'
|
| 'dashboard.import-external'
|
||||||
| 'dashboard.rename-resume'
|
| 'dashboard.rename-resume'
|
||||||
| 'builder.sections.profile'
|
| 'builder.sections.profile'
|
||||||
|
| 'builder.sections.work'
|
||||||
| `builder.sections.${string}`;
|
| `builder.sections.${string}`;
|
||||||
|
|
||||||
export type ModalState = {
|
export type ModalState = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ListItem, Profile, Resume, Section } from '@reactive-resume/schema';
|
import { ListItem, Profile, Resume, Section, SectionType } from '@reactive-resume/schema';
|
||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
@ -7,6 +7,8 @@ import pick from 'lodash/pick';
|
|||||||
import set from 'lodash/set';
|
import set from 'lodash/set';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
import { getSectionsByType } from '@/config/sections';
|
||||||
|
|
||||||
type SetResumeStatePayload = { path: string; value: unknown };
|
type SetResumeStatePayload = { path: string; value: unknown };
|
||||||
|
|
||||||
type AddItemPayload = { path: string; value: ListItem };
|
type AddItemPayload = { path: string; value: ListItem };
|
||||||
@ -17,7 +19,7 @@ type DuplicateItemPayload = { path: string; value: ListItem };
|
|||||||
|
|
||||||
type DeleteItemPayload = { path: string; value: ListItem };
|
type DeleteItemPayload = { path: string; value: ListItem };
|
||||||
|
|
||||||
type AddSectionPayload = { value: Section };
|
type AddSectionPayload = { value: Section; type: SectionType };
|
||||||
|
|
||||||
type DeleteSectionPayload = { path: string };
|
type DeleteSectionPayload = { path: string };
|
||||||
|
|
||||||
@ -80,6 +82,15 @@ export const resumeSlice = createSlice({
|
|||||||
state.sections[id] = value;
|
state.sections[id] = value;
|
||||||
state.metadata.layout[0][0].push(id);
|
state.metadata.layout[0][0].push(id);
|
||||||
},
|
},
|
||||||
|
duplicateSection: (state: Resume, action: PayloadAction<AddSectionPayload>) => {
|
||||||
|
const { value, type } = action.payload;
|
||||||
|
|
||||||
|
const id = getSectionsByType(state.sections, type).length + 1;
|
||||||
|
value.name = value.name + '-' + id;
|
||||||
|
|
||||||
|
state.sections[`${type}-${id}`] = value;
|
||||||
|
state.metadata.layout[0][0].push(`${type}-${id}`);
|
||||||
|
},
|
||||||
deleteSection: (state: Resume, action: PayloadAction<DeleteSectionPayload>) => {
|
deleteSection: (state: Resume, action: PayloadAction<DeleteSectionPayload>) => {
|
||||||
const { path } = action.payload;
|
const { path } = action.payload;
|
||||||
const id = path.split('.')[1];
|
const id = path.split('.')[1];
|
||||||
@ -119,6 +130,7 @@ export const {
|
|||||||
duplicateItem,
|
duplicateItem,
|
||||||
deleteItem,
|
deleteItem,
|
||||||
addSection,
|
addSection,
|
||||||
|
duplicateSection,
|
||||||
deleteSection,
|
deleteSection,
|
||||||
addPage,
|
addPage,
|
||||||
deletePage,
|
deletePage,
|
||||||
|
|||||||
0
client/templates/Pikachu/widgets/Timeline.tsx
Normal file
0
client/templates/Pikachu/widgets/Timeline.tsx
Normal file
@ -1,3 +1,4 @@
|
|||||||
|
import { find } from 'lodash';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { validate } from 'uuid';
|
import { validate } from 'uuid';
|
||||||
@ -44,11 +45,21 @@ const sectionMap = (Section: React.FC<SectionProps>): Record<string, JSX.Element
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const getSectionById = (id: string, Section: React.FC<SectionProps>): JSX.Element => {
|
export const getSectionById = (id: string, Section: React.FC<SectionProps>): JSX.Element => {
|
||||||
|
// Check if section id is a custom section (an uuid)
|
||||||
if (validate(id)) {
|
if (validate(id)) {
|
||||||
return <Section key={id} path={`sections.${id}`} />;
|
return <Section key={id} path={`sections.${id}`} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return get(sectionMap(Section), id);
|
// Check if section id is a predefined seciton in config
|
||||||
|
const predefinedSection = get(sectionMap(Section), id);
|
||||||
|
|
||||||
|
if(predefinedSection) {
|
||||||
|
return predefinedSection;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other ways section should be a cloned section
|
||||||
|
const section = find(sectionMap(Section), (element, key) => id.includes(key));
|
||||||
|
return React.cloneElement(section!, { path: `sections.${id}` });
|
||||||
};
|
};
|
||||||
|
|
||||||
export default sectionMap;
|
export default sectionMap;
|
||||||
|
|||||||
@ -125,7 +125,22 @@ export type ListItem =
|
|||||||
| WorkExperience
|
| WorkExperience
|
||||||
| Custom;
|
| Custom;
|
||||||
|
|
||||||
export type SectionType = 'basic' | 'custom';
|
export type SectionType =
|
||||||
|
| 'basic'
|
||||||
|
| 'location'
|
||||||
|
| 'profiles'
|
||||||
|
| 'education'
|
||||||
|
| 'awards'
|
||||||
|
| 'certifications'
|
||||||
|
| 'publications'
|
||||||
|
| 'skills'
|
||||||
|
| 'languages'
|
||||||
|
| 'interests'
|
||||||
|
| 'volunteer'
|
||||||
|
| 'projects'
|
||||||
|
| 'references'
|
||||||
|
| 'custom'
|
||||||
|
| 'work';
|
||||||
|
|
||||||
export type SectionPath = `sections.${string}`;
|
export type SectionPath = `sections.${string}`;
|
||||||
|
|
||||||
@ -136,4 +151,5 @@ export type Section = {
|
|||||||
columns: number;
|
columns: number;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
items: ListItem[];
|
items: ListItem[];
|
||||||
|
isDuplicated: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -38,7 +38,7 @@ const defaultState: Partial<Resume> = {
|
|||||||
work: {
|
work: {
|
||||||
id: 'work',
|
id: 'work',
|
||||||
name: 'Work Experience',
|
name: 'Work Experience',
|
||||||
type: 'basic',
|
type: 'work',
|
||||||
columns: 2,
|
columns: 2,
|
||||||
visible: true,
|
visible: true,
|
||||||
items: [],
|
items: [],
|
||||||
|
|||||||
Reference in New Issue
Block a user