computer-scienceangular-roadmapbackend-roadmapblockchain-roadmapdba-roadmapdeveloper-roadmapdevops-roadmapfrontend-roadmapgo-roadmaphactoberfestjava-roadmapjavascript-roadmapnodejs-roadmappython-roadmapqa-roadmapreact-roadmaproadmapstudy-planvue-roadmapweb3-roadmap
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
206 lines
5.9 KiB
206 lines
5.9 KiB
import { useEffect, useRef, useState } from 'preact/hooks'; |
|
import { wireframeJSONToSVG } from 'roadmap-renderer'; |
|
import { Spinner } from '../ReactIcons/Spinner'; |
|
import { httpGet, httpPut } from '../../lib/http'; |
|
import { renderTopicProgress } from '../../lib/resource-progress'; |
|
import '../FrameRenderer/FrameRenderer.css'; |
|
import { useOutsideClick } from '../../hooks/use-outside-click'; |
|
import { useKeydown } from '../../hooks/use-keydown'; |
|
import type { TeamResourceConfig } from './RoadmapSelector'; |
|
import { useToast } from '../../hooks/use-toast'; |
|
|
|
export type ProgressMapProps = { |
|
teamId: string; |
|
resourceId: string; |
|
resourceType: 'roadmap' | 'best-practice'; |
|
defaultRemovedItems?: string[]; |
|
setTeamResourceConfig: (config: TeamResourceConfig) => void; |
|
onClose: () => void; |
|
}; |
|
|
|
export function UpdateTeamResourceModal(props: ProgressMapProps) { |
|
const { |
|
defaultRemovedItems = [], |
|
resourceId, |
|
resourceType, |
|
teamId, |
|
setTeamResourceConfig, |
|
onClose, |
|
} = props; |
|
|
|
const containerEl = useRef<HTMLDivElement>(null); |
|
const popupBodyEl = useRef<HTMLDivElement>(null); |
|
|
|
const toast = useToast(); |
|
const [isLoading, setIsLoading] = useState(true); |
|
const [isUpdating, setIsUpdating] = useState(false); |
|
|
|
const [removedItems, setRemovedItems] = |
|
useState<string[]>(defaultRemovedItems); |
|
|
|
useEffect(() => { |
|
function onTopicClick(e: any) { |
|
const groupEl = e.target.closest('.clickable-group'); |
|
const groupId = groupEl?.dataset?.groupId; |
|
|
|
if (!groupId) { |
|
return; |
|
} |
|
|
|
const normalizedGroupId = groupId.replace(/^\d+-/, ''); |
|
if (removedItems.includes(normalizedGroupId)) { |
|
setRemovedItems((prev) => |
|
prev.filter((id) => id !== normalizedGroupId) |
|
); |
|
renderTopicProgress(normalizedGroupId, 'reset' as any); |
|
} else { |
|
setRemovedItems((prev) => [...prev, normalizedGroupId]); |
|
renderTopicProgress(normalizedGroupId, 'removed'); |
|
} |
|
} |
|
|
|
document.addEventListener('click', onTopicClick); |
|
return () => { |
|
document.removeEventListener('click', onTopicClick); |
|
}; |
|
}, [removedItems]); |
|
|
|
let resourceJsonUrl = 'https://roadmap.sh'; |
|
if (resourceType === 'roadmap') { |
|
resourceJsonUrl += `/${resourceId}.json`; |
|
} else { |
|
resourceJsonUrl += `/best-practices/${resourceId}.json`; |
|
} |
|
|
|
async function renderResource(jsonUrl: string) { |
|
const res = await fetch(jsonUrl); |
|
const json = await res.json(); |
|
const svg = await wireframeJSONToSVG(json, { |
|
fontURL: '/fonts/balsamiq.woff2', |
|
}); |
|
|
|
containerEl.current?.replaceChildren(svg); |
|
|
|
// Render team configuration |
|
removedItems.forEach((topicId: string) => { |
|
renderTopicProgress(topicId, 'removed'); |
|
}); |
|
} |
|
|
|
useKeydown('Escape', () => { |
|
onClose(); |
|
}); |
|
|
|
useOutsideClick(popupBodyEl, () => { |
|
onClose(); |
|
}); |
|
|
|
async function onSaveChanges() { |
|
if (removedItems.length === 0) { |
|
return; |
|
} |
|
|
|
setIsUpdating(true); |
|
const { error, response } = await httpPut<TeamResourceConfig>( |
|
`${ |
|
import.meta.env.PUBLIC_API_URL |
|
}/v1-update-team-resource-config/${teamId}`, |
|
{ |
|
teamId: teamId, |
|
resourceId: resourceId, |
|
resourceType: resourceType, |
|
removed: removedItems, |
|
} |
|
); |
|
|
|
if (error || !response) { |
|
toast.error(error?.message || 'Error adding roadmap'); |
|
return; |
|
} |
|
|
|
setTeamResourceConfig(response); |
|
onClose(); |
|
} |
|
|
|
useEffect(() => { |
|
if ( |
|
!containerEl.current || |
|
!resourceJsonUrl || |
|
!resourceId || |
|
!resourceType || |
|
!teamId |
|
) { |
|
return; |
|
} |
|
|
|
renderResource(resourceJsonUrl) |
|
.catch((err) => { |
|
console.error(err); |
|
toast.error('Something went wrong. Please try again!'); |
|
}) |
|
.finally(() => { |
|
setIsLoading(false); |
|
}); |
|
}, []); |
|
|
|
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" |
|
> |
|
<div |
|
className={ |
|
'sticky top-0 mb-3 rounded-2xl border-4 border-white bg-black p-4' |
|
} |
|
> |
|
<p className="mb-2 text-gray-300"> |
|
Click and select the items to remove from the roadmap. |
|
</p> |
|
<div className="flex flex-row items-center gap-1.5"> |
|
<button |
|
disabled={removedItems.length === 0} |
|
onClick={() => |
|
onSaveChanges().finally(() => setIsUpdating(false)) |
|
} |
|
className={ |
|
'rounded-md bg-blue-600 px-2.5 py-1.5 text-sm text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:bg-blue-400' |
|
} |
|
> |
|
{isUpdating ? ( |
|
<span className={'flex items-center gap-1.5'}> |
|
<Spinner |
|
className="h-3 w-3" |
|
innerFill="white" |
|
isDualRing={false} |
|
/>{' '} |
|
Saving .. |
|
</span> |
|
) : ( |
|
'Save Changes' |
|
)} |
|
</button> |
|
<button |
|
onClick={onClose} |
|
className="rounded-md bg-gray-600 px-2.5 py-1.5 text-sm text-white hover:bg-gray-700" |
|
> |
|
Cancel |
|
</button> |
|
</div> |
|
</div> |
|
<div ref={containerEl} className="px-4"></div> |
|
|
|
{isLoading && ( |
|
<div class="flex w-full justify-center"> |
|
<Spinner |
|
isDualRing={false} |
|
className="mb-4 mt-2 h-4 w-4 animate-spin fill-blue-600 text-gray-200 sm:h-8 sm:w-8" |
|
/> |
|
</div> |
|
)} |
|
</div> |
|
</div> |
|
</div> |
|
); |
|
}
|
|
|