adding skills to experiences, profiles, and projects
This commit is contained in:
@@ -9,11 +9,15 @@ import { AxiosError, AxiosResponse } from "axios";
|
||||
import { ScreenSize } from "../../enums/screenSize";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSave, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { useAppContext } from "../../hooks/appContext/UseAppContext";
|
||||
|
||||
const SkillCategoryForm = ({ isDrawerOpen, mode, onOpenClose, categoryId }: SkillCategoryFormProps) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const appContext = useAppContext();
|
||||
const defaultValues = {
|
||||
name: ''
|
||||
name: '',
|
||||
createdBy: '',
|
||||
updatedBy: ''
|
||||
}
|
||||
const methods = useForm({
|
||||
defaultValues: defaultValues
|
||||
@@ -26,14 +30,25 @@ const SkillCategoryForm = ({ isDrawerOpen, mode, onOpenClose, categoryId }: Skil
|
||||
onOpenClose(FormMode.CANCEL)
|
||||
}
|
||||
|
||||
const onSubmit = async (data: unknown) => {
|
||||
const onSubmit = async (data: any) => {
|
||||
try {
|
||||
dispatch({ type: 'SET_IS_LOADING', payload: true })
|
||||
|
||||
if (!categoryId) {
|
||||
await httpClient.post(`api/skill-categories`, data);
|
||||
const newData = {
|
||||
...data,
|
||||
createdBy: appContext.state.userProfile.userPrincipalName,
|
||||
updatedBy: appContext.state.userProfile.userPrincipalName
|
||||
}
|
||||
|
||||
await httpClient.post(`api/skill-categories`, newData);
|
||||
} else {
|
||||
await httpClient.put(`api/skill-categories`)
|
||||
const newData = {
|
||||
...data,
|
||||
updatedBy: appContext.state.userProfile.userPrincipalName
|
||||
}
|
||||
|
||||
await httpClient.put(`api/skill-categories`, newData)
|
||||
}
|
||||
|
||||
methods.reset(defaultValues);
|
||||
|
||||
@@ -9,14 +9,18 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faSave, faXmark } from '@fortawesome/free-solid-svg-icons';
|
||||
import { ScreenSize } from '../../enums/screenSize';
|
||||
import { useBreakpoints } from '../../hooks/breakpoints/UseBreakpoints';
|
||||
import { useAppContext } from '../../hooks/appContext/UseAppContext';
|
||||
|
||||
const SkillForm = ({ isDrawerOpen, mode, onOpenClose, skillId }: SkillFormProps) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const appContext = useAppContext();
|
||||
const defaultValues = {
|
||||
name: '',
|
||||
categoryId: '',
|
||||
levelId: '',
|
||||
rankId: ''
|
||||
rankId: '',
|
||||
createdBy: '',
|
||||
updatedBy: ''
|
||||
}
|
||||
const methods = useForm({
|
||||
defaultValues: defaultValues
|
||||
@@ -29,14 +33,25 @@ const SkillForm = ({ isDrawerOpen, mode, onOpenClose, skillId }: SkillFormProps)
|
||||
onOpenClose(FormMode.CANCEL);
|
||||
};
|
||||
|
||||
const onSubmit = async (data: unknown) => {
|
||||
const onSubmit = async (data: any) => {
|
||||
try {
|
||||
dispatch({ type: 'SET_IS_LOADING', payload: true });
|
||||
console.log(data)
|
||||
if (!skillId) {
|
||||
await httpClient.post(`api/skills`, data);
|
||||
const newData = {
|
||||
...data,
|
||||
createdBy: appContext.state.userProfile.userPrincipalName,
|
||||
updatedBy: appContext.state.userProfile.userPrincipalName
|
||||
}
|
||||
|
||||
await httpClient.post(`api/skills`, newData);
|
||||
} else {
|
||||
await httpClient.put(`api/skills/skill/${skillId}`, data);
|
||||
const newData = {
|
||||
...data,
|
||||
updatedBy: appContext.state.userProfile.userPrincipalName
|
||||
}
|
||||
|
||||
await httpClient.put(`api/skills/skill/${skillId}`, newData);
|
||||
}
|
||||
|
||||
methods.reset(defaultValues);
|
||||
|
||||
@@ -12,14 +12,17 @@ import { useBreakpoints } from '../../hooks/breakpoints/UseBreakpoints';
|
||||
import { Profile } from '../profiles/Profile.interface';
|
||||
import { useProfiles } from '../../hooks/profiles/UseProfiles';
|
||||
import { useAppContext } from '../../hooks/appContext/UseAppContext';
|
||||
import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/react';
|
||||
import { useSkills } from '../../hooks/skills/UseSkills';
|
||||
import MultiSelectDropdown from '../multiSelectDropdown/MultiSelectDropdown';
|
||||
import { Skill } from '../skills/Skill.interface';
|
||||
import { Experience } from '../experiences/Experience.interface';
|
||||
|
||||
const ExperienceForm = ({ isDrawerOpen, mode, onOpenClose, experienceId }: ExperienceFormProps) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const appContext = useAppContext();
|
||||
const { profiles } = useProfiles();
|
||||
const { skills } = useSkills();
|
||||
const experienceSkills: Skill[] = []
|
||||
const defaultValues = {
|
||||
profileId: '',
|
||||
companyName: '',
|
||||
@@ -27,7 +30,7 @@ const ExperienceForm = ({ isDrawerOpen, mode, onOpenClose, experienceId }: Exper
|
||||
title: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
skills: [],
|
||||
skills: experienceSkills,
|
||||
summary: '',
|
||||
statusId: 1,
|
||||
createdBy: '',
|
||||
@@ -36,9 +39,23 @@ const ExperienceForm = ({ isDrawerOpen, mode, onOpenClose, experienceId }: Exper
|
||||
const methods = useForm({
|
||||
defaultValues: defaultValues
|
||||
});
|
||||
// const watchRepoName = methods.watch(['repoName'])
|
||||
|
||||
const { screenSize } = useBreakpoints();
|
||||
|
||||
const onSetSelectedSkills = (selectedSkill: Skill) => {
|
||||
const selectedSkillExists = state.selectedSkills.find((skill) => skill.id === selectedSkill.id)
|
||||
let newSelectedSkills: Skill[];
|
||||
|
||||
if (selectedSkillExists) {
|
||||
newSelectedSkills = state.selectedSkills.filter((skill) => skill.id !== selectedSkillExists.id)
|
||||
} else {
|
||||
newSelectedSkills = [...state.selectedSkills, selectedSkill]
|
||||
}
|
||||
|
||||
dispatch({ type: 'SET_SELECTED_SKILLS', payload: newSelectedSkills })
|
||||
methods.setValue('skills', newSelectedSkills)
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
methods.reset(defaultValues);
|
||||
dispatch({ type: 'SET_IS_DISABLED', payload: false });
|
||||
@@ -91,6 +108,10 @@ const ExperienceForm = ({ isDrawerOpen, mode, onOpenClose, experienceId }: Exper
|
||||
`api/experiences/${experienceId}`
|
||||
);
|
||||
const experience = response.data;
|
||||
|
||||
if (experience.skills) {
|
||||
dispatch({ type: 'SET_SELECTED_SKILLS', payload: experience.skills })
|
||||
}
|
||||
|
||||
methods.reset(experience);
|
||||
} catch (error) {
|
||||
@@ -127,7 +148,9 @@ const ExperienceForm = ({ isDrawerOpen, mode, onOpenClose, experienceId }: Exper
|
||||
}, [profiles]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({ type: 'SET_SKILL_OPTIONS', payload: skills })
|
||||
if (skills) {
|
||||
dispatch({ type: 'SET_SKILL_OPTIONS', payload: skills })
|
||||
}
|
||||
}, [skills])
|
||||
|
||||
return (
|
||||
@@ -296,23 +319,12 @@ const ExperienceForm = ({ isDrawerOpen, mode, onOpenClose, experienceId }: Exper
|
||||
<Controller
|
||||
name="skills"
|
||||
control={methods.control}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
// <input
|
||||
// type='text'
|
||||
// className='input w-full'
|
||||
// disabled={state.isDisabled}
|
||||
// onChange={onChange}
|
||||
// value={value}
|
||||
// />
|
||||
<Listbox value={value} onChange={onChange}>
|
||||
<ListboxOptions anchor='bottom'>
|
||||
{state.skillOptions?.map((skill) => (
|
||||
<ListboxOption key={skill.id} value={skill.id}>
|
||||
{skill.name}
|
||||
</ListboxOption>
|
||||
))}
|
||||
</ListboxOptions>
|
||||
</Listbox>
|
||||
render={() => (
|
||||
<MultiSelectDropdown
|
||||
skills={state.skillOptions}
|
||||
onChange={onSetSelectedSkills}
|
||||
selectedSkills={state.selectedSkills}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -6,5 +6,6 @@ export interface ExperienceFormState {
|
||||
isDisabled: boolean;
|
||||
isLoading: boolean;
|
||||
profileOptions: Profile[];
|
||||
skillOptions: Skill[] | undefined;
|
||||
selectedSkills: Skill[];
|
||||
skillOptions: Skill[];
|
||||
}
|
||||
@@ -7,13 +7,15 @@ type Action =
|
||||
| { type: 'SET_IS_DISABLED'; payload: boolean }
|
||||
| { type: 'SET_IS_LOADING'; payload: boolean }
|
||||
| { type: 'SET_PROFILE_OPTIONS'; payload: Profile[] }
|
||||
| { type: 'SET_SKILL_OPTIONS'; payload: Skill[] | undefined };
|
||||
| { type: 'SET_SELECTED_SKILLS'; payload: Skill[] }
|
||||
| { type: 'SET_SKILL_OPTIONS'; payload: Skill[] };
|
||||
|
||||
export const initialState: ExperienceFormState = {
|
||||
error: undefined,
|
||||
isDisabled: false,
|
||||
isLoading: true,
|
||||
profileOptions: [],
|
||||
selectedSkills: [],
|
||||
skillOptions: []
|
||||
};
|
||||
|
||||
@@ -46,6 +48,12 @@ export const reducer = (
|
||||
profileOptions: action.payload
|
||||
}
|
||||
}
|
||||
case 'SET_SELECTED_SKILLS': {
|
||||
return {
|
||||
...state,
|
||||
selectedSkills: action.payload
|
||||
}
|
||||
}
|
||||
case 'SET_SKILL_OPTIONS': {
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface Experience {
|
||||
startDate: Date;
|
||||
endDate?: Date;
|
||||
summary?: string;
|
||||
skills: Skill[];
|
||||
skills?: Skill[];
|
||||
profile: Profile;
|
||||
status: Status;
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { useUserRole } from '../../hooks/userRole/UseUserRole';
|
||||
import Alert from '../../alert/Alert';
|
||||
import { CellContext, ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, useReactTable } from '@tanstack/react-table';
|
||||
import { Profile } from '../profiles/Profile.interface';
|
||||
import { Skill } from '../skills/Skill.interface';
|
||||
|
||||
interface ActionsProps {
|
||||
id: string;
|
||||
@@ -166,6 +167,17 @@ const Experiences = () => {
|
||||
accessorKey: 'summary',
|
||||
header: 'Summary'
|
||||
},
|
||||
{
|
||||
id: 'skills',
|
||||
accessorKey: 'skills',
|
||||
header: 'Skills',
|
||||
cell: (info: CellContext<Experience, unknown>) => {
|
||||
const skills = info.getValue() as Skill[];
|
||||
const skillsString: string = skills.map((skill) => skill.name).join(', ')
|
||||
|
||||
return skillsString
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
accessorKey: 'status',
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { MultiSelectDropdownProps } from "./MultiSelectDropdownProps";
|
||||
import { Skill } from "../skills/Skill.interface";
|
||||
|
||||
const MultiSelectDropdown = ({ options }: MultiSelectDropdownProps) => {
|
||||
const MultiSelectDropdown = ({ onChange, skills, selectedSkills }: MultiSelectDropdownProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedValues, setSelectedValues] = useState([])
|
||||
// const [selectedValues, setSelectedValues] = useState([])
|
||||
const dropdownRef = useRef(null)
|
||||
|
||||
const handleToggleOption = (value: any) => {
|
||||
let updated;
|
||||
// let updated;
|
||||
|
||||
if (selectedValues.includes(value)) {
|
||||
updated = selectedValues.filter((item) => item !== value);
|
||||
} else {
|
||||
updated = [...selectedValues, value];
|
||||
}
|
||||
// if (selectedValues.includes(value)) {
|
||||
// updated = selectedValues.filter((item) => item !== value);
|
||||
// } else {
|
||||
// updated = [...selectedValues, value];
|
||||
// }
|
||||
|
||||
setSelectedValues(updated);
|
||||
if (onChange) onChange(updated)
|
||||
// setSelectedValues(updated);
|
||||
// if (onChange) onChange(updated)
|
||||
}
|
||||
|
||||
const handleRemoveBadge = (event, value) => {
|
||||
event.stopPropagation();
|
||||
// event.stopPropagation();
|
||||
|
||||
const updated = selectedValues.filter((item) => item !== value);
|
||||
// const updated = selectedValues.filter((item) => item !== value);
|
||||
|
||||
setSelectedValues(updated)
|
||||
// setSelectedValues(updated)
|
||||
|
||||
if (onChange) onchange(updated)
|
||||
// if (onChange) onchange(updated)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -50,21 +51,15 @@ const MultiSelectDropdown = ({ options }: MultiSelectDropdownProps) => {
|
||||
className="select select-bordered w-full h-auto min-h-12 flex flex-wrap items-center gap-1 p-2 bg-base-100 text-left cursor-pointer"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
{selectedValues.length === 0 ? (
|
||||
{selectedSkills.length === 0 ? (
|
||||
<span className="text-base-content/50"></span>
|
||||
) : (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{selectedValues.map((val) => {
|
||||
const option = options.find((o: any) => o.value === val);
|
||||
{selectedSkills.map((selectedSkill: Skill) => {
|
||||
const option = skills.find((skill: Skill) => skill.id === selectedSkill.id);
|
||||
return (
|
||||
<div key={val} className="badge badge-primary gap-1 py-3 px-2">
|
||||
{option?.label || val}
|
||||
<button
|
||||
onClick={(event) => handleRemoveBadge(event, val)}
|
||||
className="btn btn-ghost btn-xs p-0 min-h-0 h-auto text-primary-content hover:bg-transparent"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
<div key={selectedSkill.id} className="badge badge-primary gap-1 py-3 px-2">
|
||||
{option?.name || selectedSkill.name}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -75,16 +70,16 @@ const MultiSelectDropdown = ({ options }: MultiSelectDropdownProps) => {
|
||||
tabIndex={0}
|
||||
className="dropdown-content menu p-2 shadow-lg bg-base-100 rounded-box w-full max-h-60 overflow-y-auto z-[1] border border-base-200"
|
||||
>
|
||||
{options.map((option) => (
|
||||
<li key={option.value} className="p-0">
|
||||
{skills.map((skill) => (
|
||||
<li key={skill.id} className="p-0">
|
||||
<label className="label cursor-pointer justify-start gap-3 px-4 py-2 hover:bg-base-200 rounded-lg w-full">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox checkbox-primary checkbox-sm"
|
||||
checked={selectedValues.includes(option.value)}
|
||||
onChange={() => handleToggleOption(option.value)}
|
||||
checked={selectedSkills.includes(skill)}
|
||||
onChange={() => onChange(skill)}
|
||||
/>
|
||||
<span className="label-text text-base-content">{option.label}</span>
|
||||
<span className="label-text text-base-content">{skill.name}</span>
|
||||
</label>
|
||||
</li>
|
||||
))}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { Skill } from "../skills/Skill.interface";
|
||||
|
||||
export interface MultiSelectDropdownProps {
|
||||
options: { label: string; value: string }[];
|
||||
onChange: (selectedSkill: Skill) => void;
|
||||
skills: Skill[];
|
||||
selectedSkills: Skill[];
|
||||
}
|
||||
@@ -10,15 +10,21 @@ import { faSave, faXmark } from '@fortawesome/free-solid-svg-icons';
|
||||
import { ScreenSize } from '../../enums/screenSize';
|
||||
import { useBreakpoints } from '../../hooks/breakpoints/UseBreakpoints';
|
||||
import { useAppContext } from '../../hooks/appContext/UseAppContext';
|
||||
import { Skill } from '../skills/Skill.interface';
|
||||
import { useSkills } from '../../hooks/skills/UseSkills';
|
||||
import MultiSelectDropdown from '../multiSelectDropdown/MultiSelectDropdown';
|
||||
|
||||
const ProfileForm = ({ isDrawerOpen, mode, onOpenClose, profileId }: ProfileFormProps) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const appContext = useAppContext();
|
||||
const { skills } = useSkills();
|
||||
const projectSkills: Skill[] = []
|
||||
const defaultValues = {
|
||||
name: '',
|
||||
headline: '',
|
||||
summary: '',
|
||||
userId: '',
|
||||
skills: projectSkills,
|
||||
statusId: 1
|
||||
}
|
||||
const methods = useForm({
|
||||
@@ -27,6 +33,20 @@ const ProfileForm = ({ isDrawerOpen, mode, onOpenClose, profileId }: ProfileForm
|
||||
// const watchRepoName = methods.watch(['repoName'])
|
||||
const { screenSize } = useBreakpoints();
|
||||
|
||||
const onSetSelectedSkills = (selectedSkill: Skill) => {
|
||||
const selectedSkillExists = state.selectedSkills.find((skill) => skill.id === selectedSkill.id)
|
||||
let newSelectedSkills: Skill[];
|
||||
|
||||
if (selectedSkillExists) {
|
||||
newSelectedSkills = state.selectedSkills.filter((skill) => skill.id !== selectedSkillExists.id)
|
||||
} else {
|
||||
newSelectedSkills = [...state.selectedSkills, selectedSkill]
|
||||
}
|
||||
|
||||
dispatch({ type: 'SET_SELECTED_SKILLS', payload: newSelectedSkills })
|
||||
methods.setValue('skills', newSelectedSkills)
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
methods.reset(defaultValues);
|
||||
dispatch({ type: 'SET_IS_DISABLED', payload: false });
|
||||
@@ -80,9 +100,13 @@ const ProfileForm = ({ isDrawerOpen, mode, onOpenClose, profileId }: ProfileForm
|
||||
const response: AxiosResponse = await httpClient.get(
|
||||
`api/profiles/${profileId}`
|
||||
);
|
||||
const entry = response.data;
|
||||
const profile = response.data;
|
||||
|
||||
methods.reset(entry);
|
||||
if (profile.skills) {
|
||||
dispatch({ type: 'SET_SELECTED_SKILLS', payload: profile.skills })
|
||||
}
|
||||
|
||||
methods.reset(profile);
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError;
|
||||
console.log(axiosError)
|
||||
@@ -97,6 +121,12 @@ const ProfileForm = ({ isDrawerOpen, mode, onOpenClose, profileId }: ProfileForm
|
||||
}
|
||||
}, [profileId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (skills) {
|
||||
dispatch({ type: 'SET_SKILL_OPTIONS', payload: skills })
|
||||
}
|
||||
}, [skills])
|
||||
|
||||
return (
|
||||
<div className='drawer drawer-end'>
|
||||
<input type='checkbox' className='drawer-toggle' onChange={() => {}} checked={isDrawerOpen} />
|
||||
@@ -175,6 +205,22 @@ const ProfileForm = ({ isDrawerOpen, mode, onOpenClose, profileId }: ProfileForm
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className={`col-span-3 self-center after:content-['*'] after:ms-0.5`}>
|
||||
<span>Skills</span>
|
||||
</div>
|
||||
<div className='col-span-9'>
|
||||
<Controller
|
||||
name="skills"
|
||||
control={methods.control}
|
||||
render={() => (
|
||||
<MultiSelectDropdown
|
||||
skills={state.skillOptions}
|
||||
onChange={onSetSelectedSkills}
|
||||
selectedSkills={state.selectedSkills}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className='col-span-12 justify-self-end self-center'>
|
||||
<button
|
||||
className='btn'
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { Skill } from "../skills/Skill.interface";
|
||||
|
||||
export interface ProfileFormState {
|
||||
error: string | undefined;
|
||||
isDisabled: boolean;
|
||||
isLoading: boolean;
|
||||
selectedSkills: Skill[];
|
||||
skillOptions: Skill[];
|
||||
}
|
||||
@@ -1,15 +1,20 @@
|
||||
import { Profile } from "../profiles/Profile.interface";
|
||||
import { Skill } from "../skills/Skill.interface";
|
||||
import { ProfileFormState } from "./ProfileFormState.interface"
|
||||
|
||||
type Action =
|
||||
| { type: 'SET_ERROR'; payload: string | undefined }
|
||||
| { type: 'SET_IS_DISABLED'; payload: boolean }
|
||||
| { type: 'SET_IS_LOADING'; payload: boolean };
|
||||
| { type: 'SET_IS_LOADING'; payload: boolean }
|
||||
| { type: 'SET_SELECTED_SKILLS'; payload: Skill[] }
|
||||
| { type: 'SET_SKILL_OPTIONS'; payload: Skill[] };
|
||||
|
||||
export const initialState: ProfileFormState = {
|
||||
error: undefined,
|
||||
isDisabled: false,
|
||||
isLoading: true
|
||||
isLoading: true,
|
||||
selectedSkills: [],
|
||||
skillOptions: []
|
||||
};
|
||||
|
||||
export const reducer = (
|
||||
@@ -34,6 +39,18 @@ export const reducer = (
|
||||
...state,
|
||||
isLoading: action.payload
|
||||
};
|
||||
}
|
||||
case 'SET_SELECTED_SKILLS': {
|
||||
return {
|
||||
...state,
|
||||
selectedSkills: action.payload
|
||||
}
|
||||
}
|
||||
case 'SET_SKILL_OPTIONS': {
|
||||
return {
|
||||
...state,
|
||||
skillOptions: action.payload
|
||||
}
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
|
||||
@@ -16,6 +16,7 @@ import { UserRole } from '../../enums/userRole';
|
||||
import { useUserRole } from '../../hooks/userRole/UseUserRole';
|
||||
import Alert from '../../alert/Alert';
|
||||
import { CellContext, ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, useReactTable } from '@tanstack/react-table';
|
||||
import { Skill } from '../skills/Skill.interface';
|
||||
|
||||
interface ActionsProps {
|
||||
id: string;
|
||||
@@ -144,6 +145,17 @@ const Profiles = () => {
|
||||
accessorKey: 'summary',
|
||||
header: 'Summary'
|
||||
},
|
||||
{
|
||||
id: 'skills',
|
||||
accessorKey: 'skills',
|
||||
header: 'Skills',
|
||||
cell: (info: CellContext<Profile, unknown>) => {
|
||||
const skills = info.getValue() as Skill[];
|
||||
const skillsString: string = skills.map((skill) => skill.name).join(', ')
|
||||
|
||||
return skillsString
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
accessorKey: 'status',
|
||||
|
||||
@@ -11,10 +11,15 @@ import { ScreenSize } from '../../enums/screenSize';
|
||||
import { useBreakpoints } from '../../hooks/breakpoints/UseBreakpoints';
|
||||
import { useProfiles } from '../../hooks/profiles/UseProfiles';
|
||||
import { Profile } from '../profiles/Profile.interface';
|
||||
import { Skill } from '../skills/Skill.interface';
|
||||
import { useSkills } from '../../hooks/skills/UseSkills';
|
||||
import MultiSelectDropdown from '../multiSelectDropdown/MultiSelectDropdown';
|
||||
|
||||
const ProjectForm = ({ isDrawerOpen, mode, onOpenClose, projectId }: ProjectFormProps) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const { profiles } = useProfiles()
|
||||
const { profiles } = useProfiles();
|
||||
const { skills } = useSkills();
|
||||
const projectSkills: Skill[] = []
|
||||
const defaultValues = {
|
||||
profileId: '',
|
||||
name: '',
|
||||
@@ -23,6 +28,7 @@ const ProjectForm = ({ isDrawerOpen, mode, onOpenClose, projectId }: ProjectForm
|
||||
repoUrl: '',
|
||||
siteUrl: '',
|
||||
order: '',
|
||||
skills: projectSkills,
|
||||
statusId: 1
|
||||
}
|
||||
const methods = useForm({
|
||||
@@ -31,6 +37,20 @@ const ProjectForm = ({ isDrawerOpen, mode, onOpenClose, projectId }: ProjectForm
|
||||
// const watchRepoName = methods.watch(['repoName'])
|
||||
const { screenSize } = useBreakpoints();
|
||||
|
||||
const onSetSelectedSkills = (selectedSkill: Skill) => {
|
||||
const selectedSkillExists = state.selectedSkills.find((skill) => skill.id === selectedSkill.id)
|
||||
let newSelectedSkills: Skill[];
|
||||
|
||||
if (selectedSkillExists) {
|
||||
newSelectedSkills = state.selectedSkills.filter((skill) => skill.id !== selectedSkillExists.id)
|
||||
} else {
|
||||
newSelectedSkills = [...state.selectedSkills, selectedSkill]
|
||||
}
|
||||
|
||||
dispatch({ type: 'SET_SELECTED_SKILLS', payload: newSelectedSkills })
|
||||
methods.setValue('skills', newSelectedSkills)
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
methods.reset(defaultValues);
|
||||
dispatch({ type: 'SET_IS_DISABLED', payload: false });
|
||||
@@ -44,7 +64,7 @@ const ProjectForm = ({ isDrawerOpen, mode, onOpenClose, projectId }: ProjectForm
|
||||
if (!projectId) {
|
||||
await httpClient.post(`api/projects`, data);
|
||||
} else {
|
||||
await httpClient.put(`api/projects/project/${projectId}`, data);
|
||||
await httpClient.put(`api/projects/${projectId}`, data);
|
||||
}
|
||||
|
||||
methods.reset(defaultValues);
|
||||
@@ -77,11 +97,15 @@ const ProjectForm = ({ isDrawerOpen, mode, onOpenClose, projectId }: ProjectForm
|
||||
dispatch({ type: 'SET_IS_LOADING', payload: true });
|
||||
|
||||
const response: AxiosResponse = await httpClient.get(
|
||||
`api/projects/project/${projectId}`
|
||||
`api/projects/${projectId}`
|
||||
);
|
||||
const entry = response.data;
|
||||
const project = response.data;
|
||||
|
||||
methods.reset(entry);
|
||||
if (project.skills) {
|
||||
dispatch({ type: 'SET_SELECTED_SKILLS', payload: project.skills })
|
||||
}
|
||||
|
||||
methods.reset(project);
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError;
|
||||
console.log(axiosError)
|
||||
@@ -107,13 +131,20 @@ const ProjectForm = ({ isDrawerOpen, mode, onOpenClose, projectId }: ProjectForm
|
||||
summary: '',
|
||||
userId: '',
|
||||
experiences: [],
|
||||
skills: []
|
||||
skills: [],
|
||||
status: null
|
||||
})
|
||||
|
||||
dispatch({ type: 'SET_PROFILE_OPTIONS', payload: newProfileOptions });
|
||||
}
|
||||
}, [profiles]);
|
||||
|
||||
useEffect(() => {
|
||||
if (skills) {
|
||||
dispatch({ type: 'SET_SKILL_OPTIONS', payload: skills })
|
||||
}
|
||||
}, [skills])
|
||||
|
||||
return (
|
||||
<div className='drawer drawer-end'>
|
||||
<input type='checkbox' className='drawer-toggle' onChange={() => {}} checked={isDrawerOpen} />
|
||||
@@ -272,6 +303,22 @@ const ProjectForm = ({ isDrawerOpen, mode, onOpenClose, projectId }: ProjectForm
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className={`col-span-3 self-center after:content-['*'] after:ms-0.5`}>
|
||||
<span>Skills</span>
|
||||
</div>
|
||||
<div className='col-span-9'>
|
||||
<Controller
|
||||
name="skills"
|
||||
control={methods.control}
|
||||
render={() => (
|
||||
<MultiSelectDropdown
|
||||
skills={state.skillOptions}
|
||||
onChange={onSetSelectedSkills}
|
||||
selectedSkills={state.selectedSkills}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className='col-span-12 justify-self-end self-center'>
|
||||
<button
|
||||
className='btn'
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { Profile } from "../profiles/Profile.interface";
|
||||
import { Skill } from "../skills/Skill.interface";
|
||||
|
||||
export interface ProjectFormState {
|
||||
error: string | undefined;
|
||||
isDisabled: boolean;
|
||||
isLoading: boolean;
|
||||
profileOptions: Profile[];
|
||||
selectedSkills: Skill[];
|
||||
skillOptions: Skill[];
|
||||
}
|
||||
@@ -1,17 +1,22 @@
|
||||
import { Profile } from "../profiles/Profile.interface";
|
||||
import { Skill } from "../skills/Skill.interface";
|
||||
import { ProjectFormState } from "./ProjectFormState.interface"
|
||||
|
||||
type Action =
|
||||
| { type: 'SET_ERROR'; payload: string | undefined }
|
||||
| { type: 'SET_IS_DISABLED'; payload: boolean }
|
||||
| { type: 'SET_IS_LOADING'; payload: boolean }
|
||||
| { type: 'SET_PROFILE_OPTIONS'; payload: Profile[] };
|
||||
| { type: 'SET_PROFILE_OPTIONS'; payload: Profile[] }
|
||||
| { type: 'SET_SELECTED_SKILLS'; payload: Skill[] }
|
||||
| { type: 'SET_SKILL_OPTIONS'; payload: Skill[] };
|
||||
|
||||
export const initialState: ProjectFormState = {
|
||||
error: undefined,
|
||||
isDisabled: false,
|
||||
isLoading: true,
|
||||
profileOptions: []
|
||||
profileOptions: [],
|
||||
selectedSkills: [],
|
||||
skillOptions: []
|
||||
};
|
||||
|
||||
export const reducer = (
|
||||
@@ -43,6 +48,18 @@ export const reducer = (
|
||||
profileOptions: action.payload
|
||||
}
|
||||
}
|
||||
case 'SET_SELECTED_SKILLS': {
|
||||
return {
|
||||
...state,
|
||||
selectedSkills: action.payload
|
||||
}
|
||||
}
|
||||
case 'SET_SKILL_OPTIONS': {
|
||||
return {
|
||||
...state,
|
||||
skillOptions: action.payload
|
||||
}
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
import { Project } from './Project.interface';
|
||||
// import {
|
||||
// Alert,
|
||||
// Box,
|
||||
// Button,
|
||||
// Card,
|
||||
// CardActions,
|
||||
// CardContent,
|
||||
// CardHeader,
|
||||
// Container,
|
||||
// Grid,
|
||||
// Icon,
|
||||
// IconName,
|
||||
// Skeleton,
|
||||
// Stack,
|
||||
// Typography
|
||||
// } from '@noahspan/noahspan-components';
|
||||
import { FormMode } from '../../enums/formMode';
|
||||
import { useEffect, useReducer } from 'react';
|
||||
import { initialState, reducer } from './reducer';
|
||||
@@ -32,6 +16,7 @@ import { UserRole } from '../../enums/userRole';
|
||||
import { useUserRole } from '../../hooks/userRole/UseUserRole';
|
||||
import Alert from '../../alert/Alert';
|
||||
import { CellContext, ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, useReactTable } from '@tanstack/react-table';
|
||||
import { Skill } from '../skills/Skill.interface';
|
||||
|
||||
interface ActionsProps {
|
||||
id: string;
|
||||
@@ -169,6 +154,17 @@ const Projects = () => {
|
||||
accessorKey: 'summary',
|
||||
header: 'Summary'
|
||||
},
|
||||
{
|
||||
id: 'skills',
|
||||
accessorKey: 'skills',
|
||||
header: 'Skills',
|
||||
cell: (info: CellContext<Project, unknown>) => {
|
||||
const skills = info.getValue() as Skill[];
|
||||
const skillsString: string = skills.map((skill) => skill.name).join(', ')
|
||||
|
||||
return skillsString
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
accessorKey: 'status',
|
||||
|
||||
Reference in New Issue
Block a user