- Biome 설정 파일 마이그레이션 및 규칙 적용 │
│ - 전체 파일 대상 포맷팅 및 린트 오류 수정 │ │ - 타입을 으로 변경하여 타입 안정성 강화 │ │ - 불필요한 import 제거 및 useEffect 의존성 배열 수정 │ │ - 파일을 /로 마이그레이션하여 타입스크립트 일관성 확보 │ │ - 에 개발 원칙 추가
This commit is contained in:
@@ -6,73 +6,103 @@ 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();
|
||||
}, [projectId, channelId]);
|
||||
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 }))] })] }));
|
||||
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,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,7 +31,9 @@ export function FeedbackCreatePage() {
|
||||
const schemaData = await getFeedbackSchema(projectId, channelId);
|
||||
setSchema(schemaData);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "폼을 불러오는 데 실패했습니다.");
|
||||
setError(
|
||||
err instanceof Error ? err.message : "폼을 불러오는 데 실패했습니다.",
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -40,7 +42,7 @@ export function FeedbackCreatePage() {
|
||||
fetchSchema();
|
||||
}, [projectId, channelId]);
|
||||
|
||||
const handleSubmit = async (formData: Record<string, any>) => {
|
||||
const handleSubmit = async (formData: Record<string, unknown>) => {
|
||||
if (!projectId || !channelId) return;
|
||||
|
||||
try {
|
||||
@@ -53,13 +55,19 @@ export function FeedbackCreatePage() {
|
||||
};
|
||||
|
||||
await createFeedback(projectId, channelId, requestData);
|
||||
setSubmitMessage("피드백이 성공적으로 등록되었습니다! 곧 목록으로 돌아갑니다.");
|
||||
setSubmitMessage(
|
||||
"피드백이 성공적으로 등록되었습니다! 곧 목록으로 돌아갑니다.",
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
navigate(`/projects/${projectId}/channels/${channelId}/feedbacks`);
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "피드백 등록 중 오류가 발생했습니다.");
|
||||
setError(
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: "피드백 등록 중 오류가 발생했습니다.",
|
||||
);
|
||||
throw err; // DynamicForm이 오류 상태를 인지하도록 re-throw
|
||||
}
|
||||
};
|
||||
@@ -95,4 +103,4 @@ export function FeedbackCreatePage() {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,95 +2,154 @@ 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 {
|
||||
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 }))] })] }));
|
||||
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,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,11 +3,8 @@ import { useParams, useNavigate } from "react-router-dom";
|
||||
import { DynamicForm } from "@/components/DynamicForm";
|
||||
import { useSyncChannelId } from "@/hooks/useSyncChannelId";
|
||||
import {
|
||||
getFeedbackSchema,
|
||||
getFeedbackById,
|
||||
updateFeedback,
|
||||
type Feedback,
|
||||
type FeedbackSchema,
|
||||
} from "@/services/feedback";
|
||||
import { ErrorDisplay } from "@/components/ErrorDisplay";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
@@ -41,8 +38,14 @@ export function FeedbackDetailPage() {
|
||||
]);
|
||||
|
||||
// 폼에서 숨길 필드 목록
|
||||
const hiddenFields = ["id", "createdAt", "updatedAt", "issues", "screenshot"];
|
||||
|
||||
const hiddenFields = [
|
||||
"id",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
"issues",
|
||||
"screenshot",
|
||||
];
|
||||
|
||||
const processedFields = fieldsData
|
||||
.filter((field) => !hiddenFields.includes(field.id))
|
||||
.map((field) => {
|
||||
@@ -73,7 +76,7 @@ export function FeedbackDetailPage() {
|
||||
fetchData();
|
||||
}, [projectId, channelId, feedbackId]);
|
||||
|
||||
const handleSubmit = async (formData: Record<string, any>) => {
|
||||
const handleSubmit = async (formData: Record<string, unknown>) => {
|
||||
if (!projectId || !channelId || !feedbackId) return;
|
||||
|
||||
try {
|
||||
@@ -81,22 +84,23 @@ export function FeedbackDetailPage() {
|
||||
setSuccessMessage(null);
|
||||
|
||||
// API에 전송할 데이터 정제 (수정 가능한 필드만 포함)
|
||||
const dataToUpdate: Record<string, any> = {};
|
||||
fields.forEach(field => {
|
||||
const dataToUpdate: Record<string, unknown> = {};
|
||||
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("피드백이 성공적으로 수정되었습니다! 곧 목록으로 돌아갑니다.");
|
||||
|
||||
setSuccessMessage(
|
||||
"피드백이 성공적으로 수정되었습니다! 곧 목록으로 돌아갑니다.",
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
navigate(`/projects/${projectId}/channels/${channelId}/feedbacks`);
|
||||
}, 2000);
|
||||
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
setError(err.message);
|
||||
@@ -130,7 +134,10 @@ export function FeedbackDetailPage() {
|
||||
ID: {feedback?.id}
|
||||
</span>
|
||||
<span className="text-sm text-slate-500">
|
||||
생성일: {feedback?.createdAt ? new Date(feedback.createdAt).toLocaleString("ko-KR") : 'N/A'}
|
||||
생성일:{" "}
|
||||
{feedback?.createdAt
|
||||
? new Date(feedback.createdAt).toLocaleString("ko-KR")
|
||||
: "N/A"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -148,4 +155,4 @@ export function FeedbackDetailPage() {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,44 +6,67 @@ 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();
|
||||
}, [projectId, channelId]);
|
||||
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 })] }));
|
||||
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,
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,7 +36,9 @@ export function FeedbackListPage() {
|
||||
const feedbacksData = await getFeedbacks(projectId, channelId);
|
||||
setFeedbacks(feedbacksData);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "데이터 로딩에 실패했습니다.");
|
||||
setError(
|
||||
err instanceof Error ? err.message : "데이터 로딩에 실패했습니다.",
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -46,7 +48,9 @@ export function FeedbackListPage() {
|
||||
}, [projectId, channelId]);
|
||||
|
||||
const handleRowClick = (row: Feedback) => {
|
||||
navigate(`/projects/${projectId}/channels/${channelId}/feedbacks/${row.id}`);
|
||||
navigate(
|
||||
`/projects/${projectId}/channels/${channelId}/feedbacks/${row.id}`,
|
||||
);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
|
||||
@@ -3,33 +3,125 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||||
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 {
|
||||
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" },
|
||||
{ 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." }) })) })] }) }))] })] })] }));
|
||||
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.",
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -90,4 +90,4 @@ export function IssueViewerPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user