Issue도 Dynamic Table 사용하도록 공유
This commit is contained in:
@@ -2,17 +2,9 @@ import { useState, useEffect, useMemo } from "react";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { DynamicForm } from "@/components/DynamicForm";
|
||||
import { useSyncChannelId } from "@/hooks/useSyncChannelId";
|
||||
import {
|
||||
getFeedbackById,
|
||||
updateFeedback,
|
||||
} from "@/services/feedback";
|
||||
import { getFeedbackById, updateFeedback } from "@/services/feedback";
|
||||
import { ErrorDisplay } from "@/components/ErrorDisplay";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
export function FeedbackDetailPage() {
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "@/services/feedback";
|
||||
import { ErrorDisplay } from "@/components/ErrorDisplay";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { Row } from "@tanstack/react-table";
|
||||
|
||||
export function FeedbackListPage() {
|
||||
useSyncChannelId(); // URL의 channelId를 전역 상태와 동기화
|
||||
@@ -53,6 +54,13 @@ export function FeedbackListPage() {
|
||||
);
|
||||
};
|
||||
|
||||
const renderExpandedRow = (row: Row<Feedback>) => (
|
||||
<div className="p-4 bg-muted rounded-md">
|
||||
<h4 className="font-bold text-lg">{row.original.title}</h4>
|
||||
<p className="mt-2 whitespace-pre-wrap">{row.original.contents}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return <div className="text-center py-10">로딩 중...</div>;
|
||||
}
|
||||
@@ -72,6 +80,7 @@ export function FeedbackListPage() {
|
||||
columns={schema}
|
||||
data={feedbacks}
|
||||
onRowClick={handleRowClick}
|
||||
renderExpandedRow={renderExpandedRow}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
83
viewer/src/pages/IssueListPage.tsx
Normal file
83
viewer/src/pages/IssueListPage.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useSettingsStore } from "@/store/useSettingsStore";
|
||||
import { DynamicTable } from "@/components/DynamicTable";
|
||||
import {
|
||||
getIssues,
|
||||
getIssueFields,
|
||||
type Issue,
|
||||
type IssueField,
|
||||
} from "@/services/issue";
|
||||
import { ErrorDisplay } from "@/components/ErrorDisplay";
|
||||
import type { Row } from "@tanstack/react-table";
|
||||
|
||||
export function IssueListPage() {
|
||||
const { projectId } = useSettingsStore();
|
||||
const _navigate = useNavigate();
|
||||
|
||||
const [schema, setSchema] = useState<IssueField[] | null>(null);
|
||||
const [issues, setIssues] = useState<Issue[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectId) return;
|
||||
|
||||
const fetchSchemaAndIssues = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const schemaData = await getIssueFields();
|
||||
setSchema(schemaData);
|
||||
|
||||
const issuesData = await getIssues(projectId);
|
||||
setIssues(issuesData);
|
||||
} catch (err) {
|
||||
setError(
|
||||
err instanceof Error ? err.message : "데이터 로딩에 실패했습니다.",
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchSchemaAndIssues();
|
||||
}, [projectId]);
|
||||
|
||||
const handleRowClick = (row: Issue) => {
|
||||
// 상세 페이지 구현 시 주석 해제
|
||||
// navigate(`/projects/${projectId}/issues/${row.id}`);
|
||||
console.log("Clicked issue:", row);
|
||||
};
|
||||
|
||||
const renderExpandedRow = (row: Row<Issue>) => (
|
||||
<div className="p-4 bg-muted rounded-md">
|
||||
<h4 className="font-bold text-lg">{row.original.name}</h4>
|
||||
<p className="mt-2 whitespace-pre-wrap">{row.original.description}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return <div className="text-center py-10">로딩 중...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-2xl font-bold">이슈 목록</h1>
|
||||
</div>
|
||||
{error && <ErrorDisplay message={error} />}
|
||||
{schema && (
|
||||
<div className="mt-6">
|
||||
<DynamicTable
|
||||
columns={schema}
|
||||
data={issues}
|
||||
onRowClick={handleRowClick}
|
||||
renderExpandedRow={renderExpandedRow}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1
viewer/src/pages/IssueViewerPage.d.ts
vendored
1
viewer/src/pages/IssueViewerPage.d.ts
vendored
@@ -1 +0,0 @@
|
||||
export declare function IssueViewerPage(): import("react/jsx-runtime").JSX.Element;
|
||||
@@ -1,93 +0,0 @@
|
||||
// src/pages/IssueViewerPage.tsx
|
||||
import { useState, useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { getIssues, type Issue } from "@/services/issue";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { ErrorDisplay } from "@/components/ErrorDisplay";
|
||||
|
||||
// 테이블 헤더 정의
|
||||
const issueTableHeaders = [
|
||||
{ key: "title", label: "Title" },
|
||||
{ key: "feedbackCount", label: "Feedback Count" },
|
||||
{ key: "description", label: "Description" },
|
||||
{ key: "status", label: "Status" },
|
||||
{ key: "createdAt", label: "Created" },
|
||||
{ key: "updatedAt", label: "Updated" },
|
||||
{ key: "category", label: "Category" },
|
||||
];
|
||||
|
||||
export function IssueViewerPage() {
|
||||
const { projectId } = useParams<{ projectId: string }>();
|
||||
const [issues, setIssues] = useState<Issue[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectId) return;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
getIssues(projectId)
|
||||
.then(setIssues)
|
||||
.catch((err) => setError((err as Error).message))
|
||||
.finally(() => setLoading(false));
|
||||
}, [projectId]);
|
||||
|
||||
if (error) {
|
||||
return <ErrorDisplay message={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>이슈 목록</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{loading && <p className="text-center">로딩 중...</p>}
|
||||
{!loading && (
|
||||
<div className="border rounded-md">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
{issueTableHeaders.map((header) => (
|
||||
<TableHead key={header.key}>{header.label}</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{issues.length > 0 ? (
|
||||
issues.map((issue) => (
|
||||
<TableRow key={issue.id}>
|
||||
{issueTableHeaders.map((header) => (
|
||||
<TableCell key={`${issue.id}-${header.key}`}>
|
||||
{String(issue[header.key] ?? "")}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={issueTableHeaders.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
표시할 이슈가 없습니다.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user