IssueResponse definition and implementation

This commit is contained in:
Oliver Bryan
2025-12-14 22:13:30 +00:00
parent c22d38e2ae
commit 36b39da1bd
5 changed files with 43 additions and 25 deletions

View File

@@ -1,4 +1,4 @@
import type { IssueRecord, ProjectRecord } from "@issue/shared"; import type { IssueResponse, ProjectRecord } from "@issue/shared";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
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";
@@ -23,8 +23,8 @@ function Index() {
}); });
}, []); }, []);
const [selectedIssue, setSelectedIssue] = useState<IssueRecord | null>(null); const [selectedIssue, setSelectedIssue] = useState<IssueResponse | null>(null);
const [issues, setIssues] = useState<IssueRecord[]>([]); const [issuesData, setIssues] = useState<IssueResponse[]>([]);
const serverURL = import.meta.env.SERVER_URL?.trim() || "http://localhost:3000"; const serverURL = import.meta.env.SERVER_URL?.trim() || "http://localhost:3000";
@@ -33,8 +33,9 @@ function Index() {
fetch(`${serverURL}/issues/${selectedProject.blob}`) fetch(`${serverURL}/issues/${selectedProject.blob}`)
.then((res) => res.json()) .then((res) => res.json())
.then((data: IssueRecord[]) => { .then((data: IssueResponse[]) => {
setIssues(data); setIssues(data);
console.log(data);
}) })
.catch((err) => { .catch((err) => {
console.error("error fetching issues:", err); console.error("error fetching issues:", err);
@@ -78,12 +79,12 @@ function Index() {
</div> </div>
{/* main body */} {/* main body */}
<div className="w-full h-full flex items-start justify-between pt-2 gap-2"> <div className="w-full h-full flex items-start justify-between pt-2 gap-2">
{selectedProject && issues.length > 0 && ( {selectedProject && issuesData.length > 0 && (
<> <>
{/* issues list (table) */} {/* issues list (table) */}
<IssuesTable <IssuesTable
project={selectedProject} project={selectedProject}
issues={issues} issuesData={issuesData}
columns={{ description: false }} columns={{ description: false }}
issueSelectAction={setSelectedIssue} issueSelectAction={setSelectedIssue}
className="border w-full flex-shrink" className="border w-full flex-shrink"
@@ -93,7 +94,7 @@ function Index() {
<div className="border w-2xl"> <div className="border w-2xl">
<IssueDetailPane <IssueDetailPane
project={selectedProject} project={selectedProject}
issue={selectedIssue} issueData={selectedIssue}
close={() => setSelectedIssue(null)} close={() => setSelectedIssue(null)}
/> />
</div> </div>

View File

@@ -1,22 +1,22 @@
import type { IssueRecord, ProjectRecord } from "@issue/shared"; import type { IssueResponse, ProjectRecord } from "@issue/shared";
import { X } from "lucide-react"; import { X } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { issueID } from "@/lib/utils"; import { issueID } from "@/lib/utils";
export function IssueDetailPane({ export function IssueDetailPane({
project, project,
issue, issueData,
close, close,
}: { }: {
project: ProjectRecord; project: ProjectRecord;
issue: IssueRecord; issueData: IssueResponse;
close: () => void; close: () => void;
}) { }) {
return ( return (
<div className="flex flex-col"> <div className="flex flex-col">
<div className="flex flex-row items-center justify-end border-b"> <div className="flex flex-row items-center justify-end border-b">
<span className="w-full px-0.5"> <span className="w-full px-0.5">
<p className="text-sm w-fit px-0.75">{issueID(project.blob, issue.number)}</p> <p className="text-sm w-fit px-0.75">{issueID(project.blob, issueData.Issue.number)}</p>
</span> </span>
<Button variant={"dummy"} onClick={close} className="px-0 py-0 w-6 h-6"> <Button variant={"dummy"} onClick={close} className="px-0 py-0 w-6 h-6">
@@ -25,8 +25,13 @@ export function IssueDetailPane({
</div> </div>
<div className="flex flex-col w-full p-2 gap-2"> <div className="flex flex-col w-full p-2 gap-2">
<h1 className="text-md">{issue.title}</h1> <h1 className="text-md">{issueData.Issue.title}</h1>
<p className="text-sm">{issue.description}</p> <p className="text-sm">{issueData.Issue.description}</p>
<p className="text-sm">
Assignee:{" "}
{issueData.User ? `${issueData.User.name} (${issueData.User.username})` : "Unassigned"}
</p>
</div> </div>
</div> </div>
); );

View File

@@ -1,18 +1,18 @@
import type { IssueRecord, ProjectRecord } from "@issue/shared"; import type { IssueResponse, ProjectRecord } from "@issue/shared";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { cn, issueID } from "@/lib/utils"; import { cn } from "@/lib/utils";
export function IssuesTable({ export function IssuesTable({
project, project,
issues, issuesData,
columns = {}, columns = {},
issueSelectAction, issueSelectAction,
className, className,
}: { }: {
project: ProjectRecord; project: ProjectRecord;
issues: IssueRecord[]; issuesData: IssueResponse[];
columns?: { id?: boolean; title?: boolean; description?: boolean; assignee?: boolean }; columns?: { id?: boolean; title?: boolean; description?: boolean; assignee?: boolean };
issueSelectAction?: (issue: IssueRecord) => void; issueSelectAction?: (issue: IssueResponse) => void;
className: string; className: string;
}) { }) {
return ( return (
@@ -31,27 +31,27 @@ export function IssuesTable({
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{issues.map((issue) => ( {issuesData.map((issueData) => (
<TableRow <TableRow
key={issue.id} key={issueData.Issue.id}
className="cursor-pointer" className="cursor-pointer"
onClick={() => { onClick={() => {
issueSelectAction?.(issue); issueSelectAction?.(issueData);
}} }}
> >
{(columns.id == null || columns.id === true) && ( {(columns.id == null || columns.id === true) && (
<TableCell className="font-medium border-r text-right"> <TableCell className="font-medium border-r text-right">
{issue.number.toString().padStart(3, "0")} {issueData.Issue.number.toString().padStart(3, "0")}
</TableCell> </TableCell>
)} )}
{(columns.title == null || columns.title === true) && ( {(columns.title == null || columns.title === true) && (
<TableCell>{issue.title}</TableCell> <TableCell>{issueData.Issue.title}</TableCell>
)} )}
{(columns.description == null || columns.description === true) && ( {(columns.description == null || columns.description === true) && (
<TableCell className="overflow-hide">{issue.description}</TableCell> <TableCell className="overflow-hide">{issueData.Issue.description}</TableCell>
)} )}
{(columns.assignee == null || columns.assignee === true) && ( {(columns.assignee == null || columns.assignee === true) && (
<TableCell className={"text-right"}>?</TableCell> <TableCell className={"text-right"}>{issueData.User?.id || "?"}</TableCell>
)} )}
</TableRow> </TableRow>
))} ))}

View File

@@ -20,3 +20,8 @@ export {
IssueSelectSchema, IssueSelectSchema,
IssueInsertSchema, IssueInsertSchema,
} from "./schema"; } from "./schema";
// Responses
export {
IssueResponse,
} from "./schema"

View File

@@ -58,3 +58,10 @@ export type ProjectInsert = z.infer<typeof ProjectInsertSchema>;
export type IssueRecord = z.infer<typeof IssueSelectSchema>; export type IssueRecord = z.infer<typeof IssueSelectSchema>;
export type IssueInsert = z.infer<typeof IssueInsertSchema>; export type IssueInsert = z.infer<typeof IssueInsertSchema>;
// Responses
export type IssueResponse = {
Issue: IssueRecord;
User?: UserRecord;
};