[web] projects(challenge): integrate skills follow up (#335)
This commit is contained in:
parent
672f26accc
commit
c3c036f1f3
|
|
@ -3,5 +3,6 @@
|
|||
"printWidth": 80,
|
||||
"proseWrap": "never",
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
"trailingComma": "all",
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import { useIntl } from 'react-intl';
|
|||
|
||||
import useFilterSearchParams from '~/hooks/useFilterSearchParams';
|
||||
|
||||
import type { PopoverContentWidth } from '~/components/ui/Popover';
|
||||
|
||||
export type ProjectsChallengeFilterType = 'checkbox' | 'skill-selection';
|
||||
export type ProjectsChallengeFilter = {
|
||||
id: ProjectsChallengeFilterKey;
|
||||
|
|
@ -21,6 +23,7 @@ export type ProjectsChallengeFilter = {
|
|||
}>;
|
||||
tooltip: string;
|
||||
type: ProjectsChallengeFilterType;
|
||||
width?: PopoverContentWidth;
|
||||
};
|
||||
|
||||
export type ProjectsChallengeFilterKey =
|
||||
|
|
@ -51,6 +54,7 @@ function useFilters() {
|
|||
id: '03TVln',
|
||||
}),
|
||||
type: 'checkbox',
|
||||
width: 'md',
|
||||
},
|
||||
{
|
||||
id: 'skills',
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import ProjectsChallengeFilterContextProvider, {
|
|||
useProjectsChallengeFilterContext,
|
||||
} from './ProjectsChallengeFilterContext';
|
||||
import ProjectsChallengeFilterSlideOut from './ProjectsChallengeFilterSlideOut';
|
||||
import ProjectsListFilterDropdown from './ProjectsListFilterDropdown';
|
||||
import ProjectsChallengeListFilter from './ProjectsChallengeListFilter';
|
||||
import type { ProjectsChallengeItem } from '../types';
|
||||
|
||||
type Props = Readonly<{
|
||||
|
|
@ -220,8 +220,8 @@ function ProjectsChallengeGridListWithFiltersImpl({ challenges }: Props) {
|
|||
}}
|
||||
/>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex gap-3 flex-wrap lg:flex-row md:flex-col flex-row">
|
||||
<div className="flex-1 w-full lg:w-auto">
|
||||
<div className="flex flex-row flex-wrap gap-3 md:flex-col lg:flex-row">
|
||||
<div className="w-full flex-1 lg:w-auto">
|
||||
<TextInput
|
||||
isLabelHidden={true}
|
||||
label="Search"
|
||||
|
|
@ -232,13 +232,13 @@ function ProjectsChallengeGridListWithFiltersImpl({ challenges }: Props) {
|
|||
onChange={onChangeQuery}
|
||||
/>
|
||||
</div>
|
||||
<div className="md:flex hidden gap-3 flex-wrap">
|
||||
<div className="hidden flex-wrap gap-3 md:flex">
|
||||
{filters.map((filter) => (
|
||||
<ProjectsListFilterDropdown key={filter.id} filter={filter} />
|
||||
<ProjectsChallengeListFilter key={filter.id} filter={filter} />
|
||||
))}
|
||||
{sortAndFilterButton}
|
||||
</div>
|
||||
<div className="md:hidden flex gap-3">{sortAndFilterButton}</div>
|
||||
<div className="flex gap-3 md:hidden">{sortAndFilterButton}</div>
|
||||
</div>
|
||||
{currentPageChallenges.length === 0 ? (
|
||||
<div className="p-24">
|
||||
|
|
@ -282,7 +282,7 @@ function ProjectsChallengeGridListWithFiltersImpl({ challenges }: Props) {
|
|||
</div>
|
||||
)}
|
||||
{totalPages > 1 && (
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center justify-between">
|
||||
<Text color="secondary" size="body3">
|
||||
<FormattedMessage
|
||||
defaultMessage="Showing {currentPageCount} out of {totalCount} projects"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
import { type ProjectsChallengeFilter } from '~/components/projects/challenges/lists/ProjectsChallengeFilterContext';
|
||||
|
||||
import ProjectsChallengePopoverFilterInput from './filters/ProjectsChallengePopoverFilterInput';
|
||||
import ProjectsChallengeSkillsFilterInput from './filters/ProjectsChallengeSkillsFilterInput';
|
||||
|
||||
type Props = Readonly<{
|
||||
filter: ProjectsChallengeFilter;
|
||||
}>;
|
||||
|
||||
export default function ProjectsChallengeListFilter({ filter }: Props) {
|
||||
return filter.type === 'skill-selection' ? (
|
||||
<ProjectsChallengeSkillsFilterInput filter={filter} />
|
||||
) : (
|
||||
<ProjectsChallengePopoverFilterInput filter={filter} />
|
||||
);
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
import { useState } from 'react';
|
||||
import { RiArrowDownSLine } from 'react-icons/ri';
|
||||
|
||||
import {
|
||||
type ProjectsChallengeFilter,
|
||||
useProjectsChallengeFilterState,
|
||||
} from '~/components/projects/challenges/lists/ProjectsChallengeFilterContext';
|
||||
import ProjectsSkillRoadmapSelectionDialog from '~/components/projects/skills/form/ProjectsSkillRoadmapSelectionDialog';
|
||||
import Button from '~/components/ui/Button';
|
||||
import CheckboxInput from '~/components/ui/CheckboxInput';
|
||||
import Popover from '~/components/ui/Popover';
|
||||
|
||||
type Props = Readonly<{
|
||||
filter: ProjectsChallengeFilter;
|
||||
}>;
|
||||
|
||||
export default function ProjectsListFilterDropdown({ filter }: Props) {
|
||||
const [selectedOptions, setSelectedOptions] = useProjectsChallengeFilterState(
|
||||
filter.id,
|
||||
);
|
||||
const [showSkillsRoadmapDialog, setShowSkillsRoadmapDialog] = useState(false);
|
||||
|
||||
const onChange = (value: string) => {
|
||||
const newFilters = new Set(selectedOptions);
|
||||
|
||||
if (newFilters.has(value)) {
|
||||
newFilters.delete(value);
|
||||
} else {
|
||||
newFilters.add(value);
|
||||
}
|
||||
|
||||
setSelectedOptions(Array.from(newFilters));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{filter.id === 'skills' ? (
|
||||
<Button
|
||||
icon={RiArrowDownSLine}
|
||||
label={filter.label}
|
||||
size="md"
|
||||
variant="secondary"
|
||||
onClick={() => setShowSkillsRoadmapDialog(true)}
|
||||
/>
|
||||
) : (
|
||||
<Popover
|
||||
trigger={
|
||||
<Button
|
||||
icon={RiArrowDownSLine}
|
||||
label={filter.label}
|
||||
size="md"
|
||||
variant="secondary"
|
||||
/>
|
||||
}
|
||||
width={filter.id === 'component-track' ? 'md' : 'sm'}>
|
||||
<div className="flex flex-col gap-y-3">
|
||||
{filter.options.map((option) => (
|
||||
<div key={option.value} className="flex items-center">
|
||||
<CheckboxInput
|
||||
label={option.label}
|
||||
size="sm"
|
||||
value={selectedOptions.includes(option.value)}
|
||||
onChange={() => onChange(option.value)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Popover>
|
||||
)}
|
||||
{showSkillsRoadmapDialog && (
|
||||
<ProjectsSkillRoadmapSelectionDialog
|
||||
defaultSkills={selectedOptions}
|
||||
isShown={showSkillsRoadmapDialog}
|
||||
onClose={() => setShowSkillsRoadmapDialog(false)}
|
||||
onComplete={(newSkills) => {
|
||||
setSelectedOptions(newSkills as Array<string>);
|
||||
setShowSkillsRoadmapDialog(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import { RiArrowDownSLine } from 'react-icons/ri';
|
||||
|
||||
import Button from '~/components/ui/Button';
|
||||
import CheckboxInput from '~/components/ui/CheckboxInput';
|
||||
import Popover from '~/components/ui/Popover';
|
||||
|
||||
import {
|
||||
type ProjectsChallengeFilter,
|
||||
useProjectsChallengeFilterState,
|
||||
} from '../ProjectsChallengeFilterContext';
|
||||
|
||||
type Props = Readonly<{
|
||||
filter: ProjectsChallengeFilter;
|
||||
}>;
|
||||
|
||||
export default function ProjectsChallengePopoverFilterInput({ filter }: Props) {
|
||||
const [selectedOptions, setSelectedOptions] = useProjectsChallengeFilterState(
|
||||
filter.id,
|
||||
);
|
||||
const onChange = (value: string) => {
|
||||
const newFilters = new Set(selectedOptions);
|
||||
|
||||
if (newFilters.has(value)) {
|
||||
newFilters.delete(value);
|
||||
} else {
|
||||
newFilters.add(value);
|
||||
}
|
||||
|
||||
setSelectedOptions(Array.from(newFilters));
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover
|
||||
trigger={
|
||||
<Button
|
||||
icon={RiArrowDownSLine}
|
||||
label={filter.label}
|
||||
size="md"
|
||||
variant="secondary"
|
||||
/>
|
||||
}
|
||||
width={filter.width ? filter.width : 'sm'}>
|
||||
<div className="flex flex-col gap-y-3">
|
||||
{filter.options.map((option) => (
|
||||
<div key={option.value} className="flex items-center">
|
||||
<CheckboxInput
|
||||
label={option.label}
|
||||
size="sm"
|
||||
value={selectedOptions.includes(option.value)}
|
||||
onChange={() => onChange(option.value)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import { useState } from 'react';
|
||||
import { RiArrowDownSLine } from 'react-icons/ri';
|
||||
|
||||
import ProjectsSkillRoadmapSelectionDialog from '~/components/projects/skills/form/ProjectsSkillRoadmapSelectionDialog';
|
||||
import Button from '~/components/ui/Button';
|
||||
|
||||
import {
|
||||
type ProjectsChallengeFilter,
|
||||
useProjectsChallengeFilterState,
|
||||
} from '../ProjectsChallengeFilterContext';
|
||||
|
||||
type Props = Readonly<{
|
||||
filter: ProjectsChallengeFilter;
|
||||
}>;
|
||||
|
||||
export default function ProjectsChallengeSkillsFilterInput({ filter }: Props) {
|
||||
const [selectedOptions, setSelectedOptions] = useProjectsChallengeFilterState(
|
||||
filter.id,
|
||||
);
|
||||
const [showSkillsRoadmapDialog, setShowSkillsRoadmapDialog] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
icon={RiArrowDownSLine}
|
||||
label={filter.label}
|
||||
size="md"
|
||||
variant="secondary"
|
||||
onClick={() => setShowSkillsRoadmapDialog(true)}
|
||||
/>
|
||||
{showSkillsRoadmapDialog && (
|
||||
<ProjectsSkillRoadmapSelectionDialog
|
||||
defaultSkills={selectedOptions}
|
||||
isShown={showSkillsRoadmapDialog}
|
||||
onClose={() => setShowSkillsRoadmapDialog(false)}
|
||||
onComplete={(newSkills) => {
|
||||
setSelectedOptions(newSkills as Array<string>);
|
||||
setShowSkillsRoadmapDialog(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"prettier": "latest",
|
||||
"prettier-plugin-tailwindcss": "^0.5.9",
|
||||
"turbo": "latest"
|
||||
},
|
||||
"engines": {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ importers:
|
|||
devDependencies:
|
||||
prettier:
|
||||
specifier: latest
|
||||
version: 3.1.0
|
||||
version: 3.2.5
|
||||
prettier-plugin-tailwindcss:
|
||||
specifier: ^0.5.9
|
||||
version: 0.5.9(prettier@3.2.5)
|
||||
turbo:
|
||||
specifier: latest
|
||||
version: 1.11.0
|
||||
|
|
@ -12100,10 +12103,56 @@ packages:
|
|||
prettier: 3.1.1
|
||||
dev: true
|
||||
|
||||
/prettier@3.1.0:
|
||||
resolution: {integrity: sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
/prettier-plugin-tailwindcss@0.5.9(prettier@3.2.5):
|
||||
resolution: {integrity: sha512-9x3t1s2Cjbut2QiP+O0mDqV3gLXTe2CgRlQDgucopVkUdw26sQi53p/q4qvGxMLBDfk/dcTV57Aa/zYwz9l8Ew==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
peerDependencies:
|
||||
'@ianvs/prettier-plugin-sort-imports': '*'
|
||||
'@prettier/plugin-pug': '*'
|
||||
'@shopify/prettier-plugin-liquid': '*'
|
||||
'@trivago/prettier-plugin-sort-imports': '*'
|
||||
prettier: ^3.0
|
||||
prettier-plugin-astro: '*'
|
||||
prettier-plugin-css-order: '*'
|
||||
prettier-plugin-import-sort: '*'
|
||||
prettier-plugin-jsdoc: '*'
|
||||
prettier-plugin-marko: '*'
|
||||
prettier-plugin-organize-attributes: '*'
|
||||
prettier-plugin-organize-imports: '*'
|
||||
prettier-plugin-style-order: '*'
|
||||
prettier-plugin-svelte: '*'
|
||||
prettier-plugin-twig-melody: '*'
|
||||
peerDependenciesMeta:
|
||||
'@ianvs/prettier-plugin-sort-imports':
|
||||
optional: true
|
||||
'@prettier/plugin-pug':
|
||||
optional: true
|
||||
'@shopify/prettier-plugin-liquid':
|
||||
optional: true
|
||||
'@trivago/prettier-plugin-sort-imports':
|
||||
optional: true
|
||||
prettier-plugin-astro:
|
||||
optional: true
|
||||
prettier-plugin-css-order:
|
||||
optional: true
|
||||
prettier-plugin-import-sort:
|
||||
optional: true
|
||||
prettier-plugin-jsdoc:
|
||||
optional: true
|
||||
prettier-plugin-marko:
|
||||
optional: true
|
||||
prettier-plugin-organize-attributes:
|
||||
optional: true
|
||||
prettier-plugin-organize-imports:
|
||||
optional: true
|
||||
prettier-plugin-style-order:
|
||||
optional: true
|
||||
prettier-plugin-svelte:
|
||||
optional: true
|
||||
prettier-plugin-twig-melody:
|
||||
optional: true
|
||||
dependencies:
|
||||
prettier: 3.2.5
|
||||
dev: true
|
||||
|
||||
/prettier@3.1.1:
|
||||
|
|
@ -12112,6 +12161,12 @@ packages:
|
|||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/prettier@3.2.5:
|
||||
resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/pretty-format@29.7.0:
|
||||
resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
|
|
|
|||
Loading…
Reference in New Issue