/** biome-ignore-all lint/correctness/useExhaustiveDependencies: <> */ import type { IssueResponse, OrganisationMemberResponse, OrganisationResponse, ProjectResponse, UserRecord, } from "@issue/shared"; import { useEffect, useRef, useState } from "react"; import AccountDialog from "@/components/account-dialog"; import { CreateIssue } from "@/components/create-issue"; import { IssueDetailPane } from "@/components/issue-detail-pane"; import { IssuesTable } from "@/components/issues-table"; import LogOutButton from "@/components/log-out-button"; import { OrganisationSelect } from "@/components/organisation-select"; import OrganisationsDialog from "@/components/organisations-dialog"; import { ProjectSelect } from "@/components/project-select"; import { ServerConfigurationDialog } from "@/components/server-configuration-dialog"; import { useAuthenticatedSession } from "@/components/session-provider"; import SmallUserDisplay from "@/components/small-user-display"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { ResizablePanel, ResizablePanelGroup, ResizableSeparator } from "@/components/ui/resizable"; import { issue, organisation, project } from "@/lib/server"; const BREATHING_ROOM = 1; export default function App() { const { user } = useAuthenticatedSession(); const organisationsRef = useRef(false); const [organisations, setOrganisations] = useState([]); const [selectedOrganisation, setSelectedOrganisation] = useState(null); const [projects, setProjects] = useState([]); const [selectedProject, setSelectedProject] = useState(null); const [issues, setIssues] = useState([]); const [selectedIssue, setSelectedIssue] = useState(null); const [members, setMembers] = useState([]); const refetchOrganisations = async (options?: { selectOrganisationId?: number }) => { try { await organisation.byUser({ userId: user.id, onSuccess: (data) => { const organisations = data as OrganisationResponse[]; organisations.sort((a, b) => a.Organisation.name.localeCompare(b.Organisation.name)); setOrganisations(organisations); let selected: OrganisationResponse | null = null; if (options?.selectOrganisationId) { const created = organisations.find( (o) => o.Organisation.id === options.selectOrganisationId, ); if (created) { selected = created; } } else { const savedId = localStorage.getItem("selectedOrganisationId"); if (savedId) { const saved = organisations.find((o) => o.Organisation.id === Number(savedId)); if (saved) { selected = saved; } } } if (!selected) { selected = organisations[0] || null; } setSelectedOrganisation(selected); }, onError: (error) => { console.error("error fetching organisations:", error); }, }); } catch (err) { console.error("error fetching organisations:", err); } }; useEffect(() => { if (organisationsRef.current) return; organisationsRef.current = true; void refetchOrganisations(); }, [user.id]); const refetchProjects = async (organisationId: number, options?: { selectProjectId?: number }) => { try { await project.byOrganisation({ organisationId, onSuccess: (data) => { const projects = data as ProjectResponse[]; projects.sort((a, b) => a.Project.name.localeCompare(b.Project.name)); setProjects(projects); let selected: ProjectResponse | null = null; if (options?.selectProjectId) { const created = projects.find((p) => p.Project.id === options.selectProjectId); if (created) { selected = created; } } else { const savedId = localStorage.getItem("selectedProjectId"); if (savedId) { const saved = projects.find((p) => p.Project.id === Number(savedId)); if (saved) { selected = saved; } } } if (!selected) { selected = projects[0] || null; } setSelectedProject(selected); }, onError: (error) => { console.error("error fetching projects:", error); }, }); } catch (err) { console.error("error fetching projects:", err); setProjects([]); } }; const refetchMembers = async (organisationId: number) => { try { await organisation.members({ organisationId, onSuccess: (data: OrganisationMemberResponse[]) => { setMembers(data.map((m) => m.User)); }, onError: (error) => { console.error("error fetching members:", error); setMembers([]); }, }); } catch (err) { console.error("error fetching members:", err); setMembers([]); } }; // fetch projects when organisation is selected useEffect(() => { setProjects([]); setSelectedProject(null); setSelectedIssue(null); setIssues([]); setMembers([]); if (!selectedOrganisation) { return; } void refetchProjects(selectedOrganisation.Organisation.id); void refetchMembers(selectedOrganisation.Organisation.id); }, [selectedOrganisation]); const refetchIssues = async () => { try { await issue.byProject({ projectId: selectedProject?.Project.id || 0, onSuccess: (data) => { const issues = data as IssueResponse[]; issues.reverse(); // newest at the bottom, but if the order has been rearranged, respect that setIssues(issues); }, onError: (error) => { console.error("error fetching issues:", error); setIssues([]); }, }); } catch (err) { console.error("error fetching issues:", err); setIssues([]); } }; const handleIssueDelete = async (issueId: number) => { setSelectedIssue(null); setIssues((prev) => prev.filter((issue) => issue.Issue.id !== issueId)); await refetchIssues(); }; // fetch issues when project is selected useEffect(() => { if (!selectedProject) return; void refetchIssues(); }, [selectedProject]); return (
{/* header area */}
{/* organisation selection */} { setSelectedOrganisation(org); localStorage.setItem("selectedOrganisationId", `${org?.Organisation.id}`); }} onCreateOrganisation={async (organisationId) => { await refetchOrganisations({ selectOrganisationId: organisationId }); }} showLabel /> {/* project selection - only shown when organisation is selected */} {selectedOrganisation && ( { setSelectedProject(project); localStorage.setItem("selectedProjectId", `${project?.Project.id}`); setSelectedIssue(null); }} onCreateProject={async (projectId) => { if (!selectedOrganisation) return; await refetchProjects(selectedOrganisation.Organisation.id, { selectProjectId: projectId, }); }} showLabel /> )} {selectedOrganisation && selectedProject && ( { if (!selectedProject) return; await refetchIssues(); }} /> )}
Server Configuration } />
{/* main body */} {selectedOrganisation && selectedProject && issues.length > 0 && ( {/* issues list (table) */} { if (issue.Issue.id === selectedIssue?.Issue.id) setSelectedIssue(null); else setSelectedIssue(issue); }} className="border w-full flex-shrink" /> {/* issue detail pane */} {selectedIssue && selectedOrganisation && ( <>
setSelectedIssue(null)} onIssueUpdate={refetchIssues} onIssueDelete={handleIssueDelete} />
)}
)}
); }