mirror of
https://github.com/hex248/sprint.git
synced 2026-02-09 02:33:02 +00:00
implemented org and project creation
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
|
/** biome-ignore-all lint/correctness/useExhaustiveDependencies: <> */
|
||||||
import type { IssueResponse, OrganisationResponse, ProjectResponse, UserRecord } from "@issue/shared";
|
import type { IssueResponse, OrganisationResponse, ProjectResponse, UserRecord } from "@issue/shared";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { CreateOrganisation } from "@/components/create-organisation";
|
||||||
|
import { CreateProject } from "@/components/create-project";
|
||||||
import { IssueDetailPane } from "@/components/issue-detail-pane";
|
import { IssueDetailPane } from "@/components/issue-detail-pane";
|
||||||
import { IssuesTable } from "@/components/issues-table";
|
import { IssuesTable } from "@/components/issues-table";
|
||||||
import LogOutButton from "@/components/log-out-button";
|
import LogOutButton from "@/components/log-out-button";
|
||||||
@@ -13,8 +16,16 @@ import {
|
|||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectSeparator,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
import { getAuthHeaders } from "@/lib/utils";
|
import { getAuthHeaders } from "@/lib/utils";
|
||||||
|
import { Button } from "./components/ui/button";
|
||||||
import { ResizablePanel, ResizablePanelGroup, ResizableSeparator } from "./components/ui/resizable";
|
import { ResizablePanel, ResizablePanelGroup, ResizableSeparator } from "./components/ui/resizable";
|
||||||
|
|
||||||
function Index() {
|
function Index() {
|
||||||
@@ -34,21 +45,55 @@ function Index() {
|
|||||||
const [issues, setIssues] = useState<IssueResponse[]>([]);
|
const [issues, setIssues] = useState<IssueResponse[]>([]);
|
||||||
const [selectedIssue, setSelectedIssue] = useState<IssueResponse | null>(null);
|
const [selectedIssue, setSelectedIssue] = useState<IssueResponse | null>(null);
|
||||||
|
|
||||||
|
const refetchOrganisations = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${serverURL}/organisation/by-user?userId=${user.id}`, {
|
||||||
|
headers: getAuthHeaders(),
|
||||||
|
});
|
||||||
|
const data = (await res.json()) as Array<OrganisationResponse>;
|
||||||
|
|
||||||
|
setOrganisations(data);
|
||||||
|
|
||||||
|
// attempt to retain selected organisation (avoid re-fetching projects and issues)
|
||||||
|
setSelectedOrganisation((prev) => {
|
||||||
|
if (!prev) return data[0] || null;
|
||||||
|
const stillExists = data.find((o) => o.Organisation.id === prev.Organisation.id);
|
||||||
|
return stillExists || data[0] || null;
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("error fetching organisations:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (organisationsRef.current) return;
|
if (organisationsRef.current) return;
|
||||||
organisationsRef.current = true;
|
organisationsRef.current = true;
|
||||||
|
void refetchOrganisations();
|
||||||
fetch(`${serverURL}/organisation/by-user?userId=${user.id}`, { headers: getAuthHeaders() })
|
|
||||||
.then((res) => res.json())
|
|
||||||
.then((data: Array<OrganisationResponse>) => {
|
|
||||||
setOrganisations(data);
|
|
||||||
setSelectedOrganisation(data[0] || null);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error("error fetching organisations:", err);
|
|
||||||
});
|
|
||||||
}, [user.id]);
|
}, [user.id]);
|
||||||
|
|
||||||
|
const refetchProjects = async (organisationId: number) => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`${serverURL}/projects/by-organisation?organisationId=${organisationId}`,
|
||||||
|
{
|
||||||
|
headers: getAuthHeaders(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = (await res.json()) as ProjectResponse[];
|
||||||
|
setProjects(data);
|
||||||
|
|
||||||
|
setSelectedProject((prev) => {
|
||||||
|
if (!prev) return data[0] || null;
|
||||||
|
const stillExists = data.find((p) => p.Project.id === prev.Project.id);
|
||||||
|
return stillExists || data[0] || null;
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("error fetching projects:", err);
|
||||||
|
setProjects([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// fetch projects when organisation is selected
|
// fetch projects when organisation is selected
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setProjects([]);
|
setProjects([]);
|
||||||
@@ -59,20 +104,7 @@ function Index() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(
|
void refetchProjects(selectedOrganisation.Organisation.id);
|
||||||
`${serverURL}/projects/by-organisation?organisationId=${selectedOrganisation.Organisation.id}`,
|
|
||||||
{
|
|
||||||
headers: getAuthHeaders(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.then((res) => res.json())
|
|
||||||
.then((data: ProjectResponse[]) => {
|
|
||||||
setProjects(data);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error("error fetching projects:", err);
|
|
||||||
setProjects([]);
|
|
||||||
});
|
|
||||||
}, [selectedOrganisation]);
|
}, [selectedOrganisation]);
|
||||||
|
|
||||||
// fetch issues when project is selected
|
// fetch issues when project is selected
|
||||||
@@ -90,9 +122,7 @@ function Index() {
|
|||||||
}, [selectedProject]);
|
}, [selectedProject]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (projects.length > 0) {
|
setSelectedProject((prev) => prev || projects[0] || null);
|
||||||
setSelectedProject(projects[0]);
|
|
||||||
}
|
|
||||||
}, [projects]);
|
}, [projects]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -137,7 +167,16 @@ function Index() {
|
|||||||
{organisation.Organisation.name}
|
{organisation.Organisation.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
{organisations.length === 0 && <>No organisations</>}
|
|
||||||
|
{organisations.length > 0 && <SelectSeparator />}
|
||||||
|
<CreateOrganisation
|
||||||
|
trigger={
|
||||||
|
<Button variant="ghost" className={"w-full"} size={"sm"}>
|
||||||
|
Create Organisation
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
completeAction={refetchOrganisations}
|
||||||
|
/>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
@@ -177,7 +216,24 @@ function Index() {
|
|||||||
{project.Project.name}
|
{project.Project.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
{projects.length === 0 && <>No projects in this organisation</>}
|
{projects.length > 0 && <SelectSeparator />}
|
||||||
|
<CreateProject
|
||||||
|
organisationId={selectedOrganisation?.Organisation.id}
|
||||||
|
trigger={
|
||||||
|
<Button
|
||||||
|
size={"sm"}
|
||||||
|
variant="ghost"
|
||||||
|
className={"w-full"}
|
||||||
|
disabled={!selectedOrganisation?.Organisation.id}
|
||||||
|
>
|
||||||
|
Create Project
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
completeAction={async () => {
|
||||||
|
if (!selectedOrganisation) return;
|
||||||
|
await refetchProjects(selectedOrganisation.Organisation.id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
)}
|
)}
|
||||||
|
|||||||
4
todo.md
4
todo.md
@@ -1,7 +1,5 @@
|
|||||||
- user settings/profile page
|
- user settings/profile page
|
||||||
- create organisation
|
|
||||||
- create project
|
|
||||||
- create issue
|
- create issue
|
||||||
- add user(s) to project
|
- add user(s) to project
|
||||||
- replace "no projects" text with create project button
|
|
||||||
- rename Project.blob to Project.key
|
- rename Project.blob to Project.key
|
||||||
|
- pressing on the left side of log out button does nothing (in user menu)
|
||||||
|
|||||||
Reference in New Issue
Block a user