js 의존성 제거

This commit is contained in:
Lectom C Han
2025-08-03 00:40:25 +09:00
parent 8db8ce668c
commit a53340e3c1
40 changed files with 96 additions and 48 deletions

View File

@@ -1,108 +0,0 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { DynamicForm } from "@/components/DynamicForm";
import { getFeedbackFields, createFeedback } from "@/services/feedback";
import { ErrorDisplay } from "@/components/ErrorDisplay";
import { Separator } from "@/components/ui/separator";
export function FeedbackCreatePage() {
const navigate = useNavigate();
const [fields, setFields] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [submitMessage, setSubmitMessage] = useState(null);
// TODO: projectId와 channelId는 URL 파라미터나 컨텍스트에서 가져와야 합니다.
const projectId = "1";
const channelId = "4";
useEffect(() => {
const fetchFields = async () => {
try {
setLoading(true);
const fieldsData = await getFeedbackFields(projectId, channelId);
// 사용자에게 보여주지 않을 필드 목록
const hiddenFields = ["id", "createdAt", "updatedAt", "issues"];
const processedFields = fieldsData
.filter((field) => !hiddenFields.includes(field.id))
.map((field) => {
// 'contents' 필드를 항상 textarea로 처리
if (field.id === "contents") {
return { ...field, type: "textarea" };
}
return field;
});
setFields(processedFields);
} catch (err) {
if (err instanceof Error) {
setError(err.message);
} else {
setError("알 수 없는 오류가 발생했습니다.");
}
} finally {
setLoading(false);
}
};
fetchFields();
}, []);
const handleSubmit = async (formData) => {
try {
setError(null);
setSubmitMessage(null);
const requestData = {
...formData,
issueNames: [],
};
await createFeedback(projectId, channelId, requestData);
setSubmitMessage(
"피드백이 성공적으로 등록되었습니다! 곧 목록으로 돌아갑니다.",
);
// 2초 후 목록 페이지로 이동
setTimeout(() => {
navigate(`/projects/${projectId}/channels/${channelId}/feedbacks`);
}, 2000);
} catch (err) {
if (err instanceof Error) {
setError(err.message);
} else {
setError("피드백 등록 중 알 수 없는 오류가 발생했습니다.");
}
throw err;
}
};
if (loading) {
return _jsx("div", {
children: "\uD3FC\uC744 \uBD88\uB7EC\uC624\uB294 \uC911...",
});
}
return _jsxs("div", {
className: "container mx-auto p-4",
children: [
_jsxs("div", {
className: "space-y-2 mb-6",
children: [
_jsx("h1", {
className: "text-2xl font-bold",
children: "\uD53C\uB4DC\uBC31 \uC791\uC131",
}),
_jsx("p", {
className: "text-muted-foreground",
children:
"\uC544\uB798 \uD3FC\uC744 \uC791\uC131\uD558\uC5EC \uD53C\uB4DC\uBC31\uC744 \uC81C\uCD9C\uD574\uC8FC\uC138\uC694.",
}),
],
}),
_jsx(Separator, {}),
_jsxs("div", {
className: "mt-6",
children: [
_jsx(DynamicForm, { fields: fields, onSubmit: handleSubmit }),
error && _jsx(ErrorDisplay, { message: error }),
submitMessage &&
_jsx("div", {
className: "mt-4 p-3 bg-green-100 text-green-800 rounded-md",
children: submitMessage,
}),
],
}),
],
});
}

View File

@@ -4,9 +4,9 @@ import { useSettingsStore } from "@/store/useSettingsStore";
import { useSyncChannelId } from "@/hooks/useSyncChannelId";
import { DynamicForm } from "@/components/DynamicForm";
import {
getFeedbackSchema,
getFeedbackFields,
createFeedback,
type FeedbackSchema,
type FeedbackField,
type CreateFeedbackRequest,
} from "@/services/feedback";
import { ErrorDisplay } from "@/components/ErrorDisplay";
@@ -17,7 +17,7 @@ export function FeedbackCreatePage() {
const navigate = useNavigate();
const { projectId, channelId } = useSettingsStore();
const [schema, setSchema] = useState<FeedbackSchema | null>(null);
const [schema, setSchema] = useState<FeedbackField[] | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const [submitMessage, setSubmitMessage] = useState<string | null>(null);
@@ -28,8 +28,13 @@ export function FeedbackCreatePage() {
const fetchSchema = async () => {
try {
setLoading(true);
const schemaData = await getFeedbackSchema(projectId, channelId);
setSchema(schemaData);
const schemaData = await getFeedbackFields(projectId, channelId);
// ID, Created, Updated, Issue 필드 제외
const filteredSchema = schemaData.filter(
(field) =>
!["id", "createdAt", "updatedAt", "issues"].includes(field.id),
);
setSchema(filteredSchema);
} catch (err) {
setError(
err instanceof Error ? err.message : "폼을 불러오는 데 실패했습니다.",
@@ -91,7 +96,7 @@ export function FeedbackCreatePage() {
<Separator />
{schema && (
<DynamicForm
schema={schema}
fields={schema}
onSubmit={handleSubmit}
submitButtonText="제출하기"
/>

View File

@@ -1,155 +0,0 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect, useMemo } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { DynamicForm } from "@/components/DynamicForm";
import {
getFeedbackFields,
getFeedbackById,
updateFeedback,
} from "@/services/feedback";
import { ErrorDisplay } from "@/components/ErrorDisplay";
import { Separator } from "@/components/ui/separator";
export function FeedbackDetailPage() {
const { projectId, channelId, feedbackId } = useParams();
const navigate = useNavigate();
const [fields, setFields] = useState([]);
const [feedback, setFeedback] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [successMessage, setSuccessMessage] = useState(null);
const initialData = useMemo(() => feedback ?? {}, [feedback]);
useEffect(() => {
const fetchData = async () => {
if (!projectId || !channelId || !feedbackId) return;
try {
setLoading(true);
const [fieldsData, feedbackData] = await Promise.all([
getFeedbackFields(projectId, channelId),
getFeedbackById(projectId, channelId, feedbackId),
]);
// 폼에서 숨길 필드 목록
const hiddenFields = [
"id",
"createdAt",
"updatedAt",
"issues",
"screenshot",
];
const processedFields = fieldsData
.filter((field) => !hiddenFields.includes(field.id))
.map((field) => {
// 'contents' 필드는 항상 textarea로
if (field.id === "contents") {
return { ...field, type: "textarea" };
}
// 'customer' 필드는 읽기 전용으로
if (field.id === "customer") {
return { ...field, readOnly: true };
}
return field;
});
setFields(processedFields);
setFeedback(feedbackData);
} catch (err) {
if (err instanceof Error) {
setError(err.message);
} else {
setError("데이터를 불러오는 중 알 수 없는 오류가 발생했습니다.");
}
} finally {
setLoading(false);
}
};
fetchData();
}, [projectId, channelId, feedbackId]);
const handleSubmit = async (formData) => {
if (!projectId || !channelId || !feedbackId) return;
try {
setError(null);
setSuccessMessage(null);
// API에 전송할 데이터 정제 (수정 가능한 필드만 포함)
const dataToUpdate = {};
fields.forEach((field) => {
if (!field.readOnly && formData[field.id] !== undefined) {
dataToUpdate[field.id] = formData[field.id];
}
});
console.log("Updating with data:", dataToUpdate); // [Debug]
await updateFeedback(projectId, channelId, feedbackId, dataToUpdate);
setSuccessMessage(
"피드백이 성공적으로 수정되었습니다! 곧 목록으로 돌아갑니다.",
);
setTimeout(() => {
navigate(`/projects/${projectId}/channels/${channelId}/feedbacks`);
}, 2000);
} catch (err) {
if (err instanceof Error) {
setError(err.message);
} else {
setError("피드백 수정 중 알 수 없는 오류가 발생했습니다.");
}
throw err;
}
};
if (loading) {
return _jsx("div", { children: "\uB85C\uB529 \uC911..." });
}
if (error) {
return _jsx(ErrorDisplay, { message: error });
}
return _jsxs("div", {
className: "container mx-auto p-4",
children: [
_jsxs("div", {
className: "space-y-2 mb-6",
children: [
_jsx("h1", {
className: "text-2xl font-bold",
children: "\uD53C\uB4DC\uBC31 \uC0C1\uC138 \uBC0F \uC218\uC815",
}),
_jsx("p", {
className: "text-muted-foreground",
children:
"\uD53C\uB4DC\uBC31 \uB0B4\uC6A9\uC744 \uD655\uC778\uD558\uACE0 \uC218\uC815\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
}),
],
}),
_jsx(Separator, {}),
_jsxs("div", {
className: "mt-6",
children: [
_jsxs("div", {
className:
"flex justify-between items-center mb-4 p-3 bg-slate-50 rounded-md",
children: [
_jsxs("span", {
className: "text-sm font-medium text-slate-600",
children: ["ID: ", feedback?.id],
}),
_jsxs("span", {
className: "text-sm text-slate-500",
children: [
"\uC0DD\uC131\uC77C: ",
feedback?.createdAt
? new Date(feedback.createdAt).toLocaleString("ko-KR")
: "N/A",
],
}),
],
}),
_jsx(DynamicForm, {
fields: fields,
initialData: initialData,
onSubmit: handleSubmit,
submitButtonText: "\uC218\uC815\uD558\uAE30",
}),
successMessage &&
_jsx("div", {
className: "mt-4 p-3 bg-green-100 text-green-800 rounded-md",
children: successMessage,
}),
],
}),
],
});
}

View File

@@ -7,6 +7,12 @@ import {
updateFeedback,
} from "@/services/feedback";
import { ErrorDisplay } from "@/components/ErrorDisplay";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
export function FeedbackDetailPage() {
@@ -128,31 +134,35 @@ export function FeedbackDetailPage() {
</p>
</div>
<Separator />
<div className="mt-6">
<div className="flex justify-between items-center mb-4 p-3 bg-slate-50 rounded-md">
<span className="text-sm font-medium text-slate-600">
ID: {feedback?.id}
</span>
<span className="text-sm text-slate-500">
:{" "}
{feedback?.createdAt
? new Date(feedback.createdAt).toLocaleString("ko-KR")
: "N/A"}
</span>
</div>
<DynamicForm
fields={fields}
initialData={initialData}
onSubmit={handleSubmit}
submitButtonText="수정하기"
/>
{successMessage && (
<div className="mt-4 p-3 bg-green-100 text-green-800 rounded-md">
{successMessage}
<Card className="mt-6">
<CardHeader>
<div className="flex justify-between items-center">
<CardTitle> </CardTitle>
<div className="flex items-center gap-4 text-sm text-slate-500">
<span>ID: {feedback?.id}</span>
<span>
:{" "}
{feedback?.createdAt
? new Date(feedback.createdAt).toLocaleString("ko-KR")
: "N/A"}
</span>
</div>
</div>
)}
</div>
</CardHeader>
<CardContent>
<DynamicForm
fields={fields}
initialData={initialData}
onSubmit={handleSubmit}
submitButtonText="수정하기"
/>
{successMessage && (
<div className="mt-4 p-3 bg-green-100 text-green-800 rounded-md">
{successMessage}
</div>
)}
</CardContent>
</Card>
</div>
);
}

View File

@@ -1,72 +0,0 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { DynamicTable } from "@/components/DynamicTable";
import { getFeedbacks, getFeedbackFields } from "@/services/feedback";
import { ErrorDisplay } from "@/components/ErrorDisplay";
import { Button } from "@/components/ui/button";
export function FeedbackListPage() {
const [fields, setFields] = useState([]);
const [feedbacks, setFeedbacks] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// TODO: projectId와 channelId는 URL 파라미터나 컨텍스트에서 가져와야 합니다.
const projectId = "1";
const channelId = "4";
useEffect(() => {
const fetchFieldsAndFeedbacks = async () => {
try {
setLoading(true);
const fieldsData = await getFeedbackFields(projectId, channelId);
setFields(fieldsData);
try {
const feedbacksData = await getFeedbacks(projectId, channelId);
setFeedbacks(feedbacksData);
} catch (feedbackError) {
console.error("Failed to fetch feedbacks:", feedbackError);
setError("피드백 목록을 불러오는 데 실패했습니다.");
}
} catch (fieldsError) {
if (fieldsError instanceof Error) {
setError(fieldsError.message);
} else {
setError("테이블 구조를 불러오는 데 실패했습니다.");
}
} finally {
setLoading(false);
}
};
fetchFieldsAndFeedbacks();
}, []);
if (loading) {
return _jsx("div", { children: "\uB85C\uB529 \uC911..." });
}
return _jsxs("div", {
className: "container mx-auto p-4",
children: [
_jsxs("div", {
className: "flex justify-between items-center mb-4",
children: [
_jsx("h1", {
className: "text-2xl font-bold",
children: "\uD53C\uB4DC\uBC31 \uBAA9\uB85D",
}),
_jsx(Button, {
asChild: true,
children: _jsx(Link, {
to: "new",
children: "\uC0C8 \uD53C\uB4DC\uBC31 \uC791\uC131",
}),
}),
],
}),
error && _jsx(ErrorDisplay, { message: error }),
_jsx(DynamicTable, {
columns: fields,
data: feedbacks,
projectId: projectId,
channelId: channelId,
}),
],
});
}

View File

@@ -5,9 +5,9 @@ import { useSyncChannelId } from "@/hooks/useSyncChannelId";
import { DynamicTable } from "@/components/DynamicTable";
import {
getFeedbacks,
getFeedbackSchema,
getFeedbackFields,
type Feedback,
type FeedbackSchema,
type FeedbackField,
} from "@/services/feedback";
import { ErrorDisplay } from "@/components/ErrorDisplay";
import { Button } from "@/components/ui/button";
@@ -17,7 +17,7 @@ export function FeedbackListPage() {
const { projectId, channelId } = useSettingsStore();
const navigate = useNavigate();
const [schema, setSchema] = useState<FeedbackSchema | null>(null);
const [schema, setSchema] = useState<FeedbackField[] | null>(null);
const [feedbacks, setFeedbacks] = useState<Feedback[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
@@ -30,7 +30,7 @@ export function FeedbackListPage() {
setLoading(true);
setError(null);
const schemaData = await getFeedbackSchema(projectId, channelId);
const schemaData = await getFeedbackFields(projectId, channelId);
setSchema(schemaData);
const feedbacksData = await getFeedbacks(projectId, channelId);
@@ -67,11 +67,13 @@ export function FeedbackListPage() {
</div>
{error && <ErrorDisplay message={error} />}
{schema && (
<DynamicTable
schema={schema}
data={feedbacks}
onRowClick={handleRowClick}
/>
<div className="mt-6">
<DynamicTable
columns={schema}
data={feedbacks}
onRowClick={handleRowClick}
/>
</div>
)}
</div>
);

View File

@@ -1,127 +0,0 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
// src/pages/IssueViewerPage.tsx
import { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import { getIssues } 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();
const [issues, setIssues] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
if (!projectId) return;
setLoading(true);
setError(null);
getIssues(projectId)
.then(setIssues)
.catch((err) => setError(err.message))
.finally(() => setLoading(false));
}, [projectId]);
return _jsxs("div", {
className: "container mx-auto p-4 md:p-8",
children: [
_jsxs("header", {
className: "mb-8",
children: [
_jsx("h1", {
className: "text-3xl font-bold tracking-tight",
children: "\uC774\uC288 \uBDF0\uC5B4",
}),
_jsxs("p", {
className: "text-muted-foreground mt-1",
children: ["\uD504\uB85C\uC81D\uD2B8: ", projectId],
}),
],
}),
error && _jsx(ErrorDisplay, { message: error }),
_jsxs(Card, {
children: [
_jsx(CardHeader, {
children: _jsx(CardTitle, {
children: "\uC774\uC288 \uBAA9\uB85D",
}),
}),
_jsxs(CardContent, {
children: [
loading &&
_jsx("p", {
className: "text-center",
children: "\uB85C\uB529 \uC911...",
}),
!loading &&
_jsx("div", {
className: "border rounded-md",
children: _jsxs(Table, {
children: [
_jsx(TableHeader, {
children: _jsx(TableRow, {
children: issueTableHeaders.map((header) =>
_jsx(
TableHead,
{ children: header.label },
header.key,
),
),
}),
}),
_jsx(TableBody, {
children:
issues.length > 0
? issues.map((issue) =>
_jsx(
TableRow,
{
children: issueTableHeaders.map((header) =>
_jsx(
TableCell,
{
children: String(
issue[header.key] ?? "",
),
},
`${issue.id}-${header.key}`,
),
),
},
issue.id,
),
)
: _jsx(TableRow, {
children: _jsx(TableCell, {
colSpan: issueTableHeaders.length,
className: "h-24 text-center",
children:
"\uD45C\uC2DC\uD560 \uC774\uC288\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
}),
}),
}),
],
}),
}),
],
}),
],
}),
],
});
}