wip: roadmap selector modal

chore/roadmaps-modal
Arik Chakma 1 year ago
parent ac48f4c441
commit c487147a63
  1. 82
      src/components/CreateTeam/RoadmapSelector.tsx
  2. 118
      src/components/CreateTeam/SelectRoadmapModal.tsx
  3. 4
      src/components/CreateTeam/Step2.tsx
  4. 1
      src/icons/plus-white.svg

@ -3,9 +3,12 @@ import { SearchSelector } from '../SearchSelector';
import { httpGet, httpPut } from '../../lib/http';
import type { PageType } from '../CommandMenu/CommandMenu';
import SearchIcon from '../../icons/search.svg';
import PlusIcon from '../../icons/plus.svg';
import PlusWhiteIcon from '../../icons/plus-white.svg';
import { pageProgressMessage } from '../../stores/page';
import type { TeamDocument } from './CreateTeamForm';
import { UpdateTeamResourceModal } from './UpdateTeamResourceModal';
import { SelectRoadmapModal } from './SelectRoadmapModal';
export type TeamResourceConfig = {
resourceId: string;
@ -22,6 +25,7 @@ type RoadmapSelectorProps = {
export function RoadmapSelector(props: RoadmapSelectorProps) {
const { team, teamResourceConfig = [], setTeamResourceConfig } = props;
const [showSelectRoadmapModal, setShowSelectRoadmapModal] = useState(false);
const [allRoadmaps, setAllRoadmaps] = useState<PageType[]>([]);
const [changingRoadmapId, setChangingRoadmapId] = useState<string>('');
const [error, setError] = useState<string>('');
@ -56,8 +60,7 @@ export function RoadmapSelector(props: RoadmapSelectorProps) {
pageProgressMessage.set(`Deleting resource`);
const { error, response } = await httpPut<TeamResourceConfig>(
`${import.meta.env.PUBLIC_API_URL}/v1-delete-team-resource-config/${
team._id
`${import.meta.env.PUBLIC_API_URL}/v1-delete-team-resource-config/${team._id
}`,
{
resourceId: roadmapId,
@ -88,8 +91,7 @@ export function RoadmapSelector(props: RoadmapSelectorProps) {
pageProgressMessage.set(`Adding roadmap to team`);
const { error, response } = await httpPut<TeamResourceConfig>(
`${import.meta.env.PUBLIC_API_URL}/v1-update-team-resource-config/${
team._id
`${import.meta.env.PUBLIC_API_URL}/v1-update-team-resource-config/${team._id
}`,
{
teamId: team._id,
@ -126,42 +128,47 @@ export function RoadmapSelector(props: RoadmapSelectorProps) {
}
/>
)}
<SearchSelector
placeholder={`Search Roadmaps ..`}
onSelect={(option) => {
const roadmapId = option.value;
addTeamResource(roadmapId).finally(() => {
pageProgressMessage.set('');
});
}}
options={allRoadmaps
.filter((roadmap) => {
return !teamResourceConfig
.map((c) => c.resourceId)
.includes(roadmap.id);
})
.map((roadmap) => ({
value: roadmap.id,
label: roadmap.title,
}))}
searchInputId={'roadmap-input'}
inputClassName="mt-2 block w-full rounded-md border px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
/>
{showSelectRoadmapModal && (
<SelectRoadmapModal
onClose={() => setShowSelectRoadmapModal(false)}
teamResourceConfig={teamResourceConfig}
allRoadmaps={allRoadmaps}
teamId={team?._id!}
onRoadmapAdd={(roadmapId) => {
addTeamResource(roadmapId).finally(() => {
pageProgressMessage.set('');
});
}}
onRoadmapRemove={(roadmapId) => {
onRemove(roadmapId).finally(() => {
});
}}
/>
)}
{!teamResourceConfig.length && (
<div className="mt-4 rounded-md border px-4 py-12 text-center text-sm text-gray-700">
<img
alt={'search'}
src={SearchIcon}
className={'mx-auto mb-5 h-[42px] w-[42px] opacity-10'}
/>
<span className="block text-lg font-semibold text-black">
No roadmaps selected.
</span>
<p className={'text-sm text-gray-400'}>
Please search and add roadmaps from above
</p>
<div className="mt-4 flex justify-center">
<button
className="group bg-black justify-center rounded-md px-4 py-2 flex items-center gap-2 transition-opacity hover:opacity-70"
onClick={() => setShowSelectRoadmapModal(true)}
>
<img
alt="add"
src={PlusWhiteIcon}
className="h-5 w-5"
/>
<span className="text-sm text-white focus:outline-none">
Add Roadmaps
</span>
</button>
</div>
</div>
)}
@ -214,6 +221,19 @@ export function RoadmapSelector(props: RoadmapSelectorProps) {
</div>
);
})}
<button
className="group flex min-h-[110px] flex-col items-center justify-center rounded-md border border-dashed border-gray-300 px-8 transition-colors hover:border-gray-600 hover:bg-gray-50"
onClick={() => setShowSelectRoadmapModal(true)}
>
<img
alt="add"
src={PlusIcon}
className="mb-1 h-6 w-6 opacity-20 transition-opacity group-hover:opacity-100"
/>
<span className="text-sm text-gray-400 transition-colors focus:outline-none group-hover:text-black">
Add Roadmaps
</span>
</button>
</div>
)}
</div>

@ -0,0 +1,118 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import { useKeydown } from '../../hooks/use-keydown';
import { useOutsideClick } from '../../hooks/use-outside-click';
import type { PageType } from '../CommandMenu/CommandMenu';
import { httpGet } from '../../lib/http';
import { useToast } from '../../hooks/use-toast';
import type { TeamResourceConfig } from './RoadmapSelector';
export type SelectRoadmapModalProps = {
teamId: string;
allRoadmaps: PageType[];
onClose: () => void;
teamResourceConfig: TeamResourceConfig
onRoadmapAdd: (roadmapId: string) => void;
onRoadmapRemove: (roadmapId: string) => void;
};
export function SelectRoadmapModal(props: SelectRoadmapModalProps) {
const { onClose, allRoadmaps, onRoadmapAdd, onRoadmapRemove, teamResourceConfig } = props;
const toast = useToast();
const popupBodyEl = useRef<HTMLDivElement>(null);
const [searchResults, setSearchResults] = useState<PageType[]>(allRoadmaps);
const [searchText, setSearchText] = useState('');
useKeydown('Escape', () => {
onClose();
});
useOutsideClick(popupBodyEl, () => {
onClose();
});
useEffect(() => {
if (searchText.length === 0) {
setSearchResults(allRoadmaps);
return;
}
const searchResults = allRoadmaps.filter(roadmap => {
return roadmap.title.toLowerCase().includes(searchText.toLowerCase()) ||
roadmap.id.toLowerCase().includes(searchText.toLowerCase());
});
setSearchResults(searchResults);
}, [searchText, allRoadmaps])
return (
<div class="fixed left-0 right-0 top-0 z-50 h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50">
<div class="relative mx-auto h-full w-full max-w-4xl p-4 md:h-auto">
<div
ref={popupBodyEl}
class="popup-body relative rounded-lg bg-white shadow overflow-hidden mt-4"
>
<input
type="text"
placeholder="Search roadmaps"
className="w-full block px-4 py-3 outline-none placeholder:text-gray-400"
value={searchText}
onInput={e => setSearchText((e.target as HTMLInputElement).value)}
/>
<div className="flex flex-wrap gap-2.5 p-4 border-t min-h-[20vh]">
{
searchResults.map(roadmap => {
const isSelected = teamResourceConfig.find(r => r.resourceId === roadmap.id);
return (
<div className={`rounded-md border flex items-center ${isSelected && 'bg-gray-100'}`}>
<span className="px-4">
{roadmap.title}
</span>
<button className="p-1 h-8 w-8 flex items-center justify-center leading-none border-l hover:bg-gray-100"
onClick={() => {
if (isSelected) {
onRoadmapRemove(roadmap.id);
} else {
onRoadmapAdd(roadmap.id);
}
}}
>
{isSelected ? '-' : '+'}
</button>
</div>
)
})
}
{
searchResults.length === 0 && (
<div className="text-gray-400">
No roadmaps found
</div>
)
}
</div>
<div className="border-t flex items-center justify-between gap-2">
<button
className="px-4 py-2 hover:bg-gray-100"
onClick={() => {
onClose();
}}
>
Close
</button>
<button
className="px-4 py-2 hover:bg-gray-100 border-l"
onClick={() => {
onClose();
}}
>
Apply Selection
</button>
</div>
</div>
</div>
</div>
);
}

@ -1,5 +1,6 @@
import { RoadmapSelector, TeamResourceConfig } from './RoadmapSelector';
import {RoadmapSelector, TeamResourceConfig } from './RoadmapSelector';
import type { TeamDocument } from './CreateTeamForm';
import { useState } from 'preact/hooks';
type Step2Props = {
team: TeamDocument;
@ -12,6 +13,7 @@ type Step2Props = {
export function Step2(props: Step2Props) {
const { team, onBack, onNext, teamResourceConfig, setTeamResourceConfig } =
props;
const [showSelectRoadmapModal, setShowSelectRoadmapModal] = useState(false);
return (
<>

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file-plus-2"><path d="M4 22h14a2 2 0 0 0 2-2V7.5L14.5 2H6a2 2 0 0 0-2 2v4"/><polyline points="14 2 14 8 20 8"/><path d="M3 15h6"/><path d="M6 12v6"/></svg>

After

Width:  |  Height:  |  Size: 353 B

Loading…
Cancel
Save