js 의존성 제거
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
// src/services/feedback.ts
|
// src/services/feedback.ts
|
||||||
import { handleApiError } from "./error";
|
import { handleApiError } from "../../../src/services/error";
|
||||||
// --- API 함수 ---
|
// --- API 함수 ---
|
||||||
const getFeedbacksSearchApiUrl = (projectId, channelId) =>
|
const getFeedbacksSearchApiUrl = (projectId, channelId) =>
|
||||||
`/api/v2/projects/${projectId}/channels/${channelId}/feedbacks/search`;
|
`/api/v2/projects/${projectId}/channels/${channelId}/feedbacks/search`;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// src/services/issue.ts
|
// src/services/issue.ts
|
||||||
import { handleApiError } from "./error";
|
import { handleApiError } from "../../../src/services/error";
|
||||||
/**
|
/**
|
||||||
* 특정 프로젝트의 모든 이슈를 검색합니다.
|
* 특정 프로젝트의 모든 이슈를 검색합니다.
|
||||||
* @param projectId 프로젝트 ID
|
* @param projectId 프로젝트 ID
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { NavLink } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
import { ProjectSelectBox } from "./ProjectSelectBox";
|
import { ProjectSelectBox } from "./ProjectSelectBox";
|
||||||
import { ThemeSelectBox } from "./ThemeSelectBox";
|
import { ThemeSelectBox } from "./ThemeSelectBox";
|
||||||
@@ -10,7 +11,11 @@ const menuItems = [
|
|||||||
{ name: "Issue", path: "/issues", type: "issue" },
|
{ name: "Issue", path: "/issues", type: "issue" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function Header() {
|
interface HeaderProps {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Header({ className }: HeaderProps) {
|
||||||
const { projectId, channelId } = useSettingsStore();
|
const { projectId, channelId } = useSettingsStore();
|
||||||
|
|
||||||
const getPath = (type: string, basePath: string) => {
|
const getPath = (type: string, basePath: string) => {
|
||||||
@@ -23,7 +28,12 @@ export function Header() {
|
|||||||
const homePath = `/projects/${projectId}`;
|
const homePath = `/projects/${projectId}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="flex h-16 items-center justify-between border-b px-6">
|
<header
|
||||||
|
className={cn(
|
||||||
|
"flex h-16 items-center justify-between border-b px-6",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
<NavLink
|
<NavLink
|
||||||
to={homePath}
|
to={homePath}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { Header } from "./Header";
|
|||||||
export function MainLayout() {
|
export function MainLayout() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col min-h-screen">
|
<div className="flex flex-col min-h-screen">
|
||||||
<Header />
|
<Header className="sticky top-0 z-50 bg-background" />
|
||||||
<main className="flex-1 p-6">
|
<main className="flex-1 container mx-auto p-6">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { useSettingsStore } from "@/store/useSettingsStore";
|
|||||||
import { useSyncChannelId } from "@/hooks/useSyncChannelId";
|
import { useSyncChannelId } from "@/hooks/useSyncChannelId";
|
||||||
import { DynamicForm } from "@/components/DynamicForm";
|
import { DynamicForm } from "@/components/DynamicForm";
|
||||||
import {
|
import {
|
||||||
getFeedbackSchema,
|
getFeedbackFields,
|
||||||
createFeedback,
|
createFeedback,
|
||||||
type FeedbackSchema,
|
type FeedbackField,
|
||||||
type CreateFeedbackRequest,
|
type CreateFeedbackRequest,
|
||||||
} from "@/services/feedback";
|
} from "@/services/feedback";
|
||||||
import { ErrorDisplay } from "@/components/ErrorDisplay";
|
import { ErrorDisplay } from "@/components/ErrorDisplay";
|
||||||
@@ -17,7 +17,7 @@ export function FeedbackCreatePage() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { projectId, channelId } = useSettingsStore();
|
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 [loading, setLoading] = useState<boolean>(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [submitMessage, setSubmitMessage] = useState<string | null>(null);
|
const [submitMessage, setSubmitMessage] = useState<string | null>(null);
|
||||||
@@ -28,8 +28,13 @@ export function FeedbackCreatePage() {
|
|||||||
const fetchSchema = async () => {
|
const fetchSchema = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const schemaData = await getFeedbackSchema(projectId, channelId);
|
const schemaData = await getFeedbackFields(projectId, channelId);
|
||||||
setSchema(schemaData);
|
// ID, Created, Updated, Issue 필드 제외
|
||||||
|
const filteredSchema = schemaData.filter(
|
||||||
|
(field) =>
|
||||||
|
!["id", "createdAt", "updatedAt", "issues"].includes(field.id),
|
||||||
|
);
|
||||||
|
setSchema(filteredSchema);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(
|
setError(
|
||||||
err instanceof Error ? err.message : "폼을 불러오는 데 실패했습니다.",
|
err instanceof Error ? err.message : "폼을 불러오는 데 실패했습니다.",
|
||||||
@@ -91,7 +96,7 @@ export function FeedbackCreatePage() {
|
|||||||
<Separator />
|
<Separator />
|
||||||
{schema && (
|
{schema && (
|
||||||
<DynamicForm
|
<DynamicForm
|
||||||
schema={schema}
|
fields={schema}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
submitButtonText="제출하기"
|
submitButtonText="제출하기"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ import {
|
|||||||
updateFeedback,
|
updateFeedback,
|
||||||
} from "@/services/feedback";
|
} from "@/services/feedback";
|
||||||
import { ErrorDisplay } from "@/components/ErrorDisplay";
|
import { ErrorDisplay } from "@/components/ErrorDisplay";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
|
||||||
export function FeedbackDetailPage() {
|
export function FeedbackDetailPage() {
|
||||||
@@ -128,31 +134,35 @@ export function FeedbackDetailPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
<div className="mt-6">
|
<Card className="mt-6">
|
||||||
<div className="flex justify-between items-center mb-4 p-3 bg-slate-50 rounded-md">
|
<CardHeader>
|
||||||
<span className="text-sm font-medium text-slate-600">
|
<div className="flex justify-between items-center">
|
||||||
ID: {feedback?.id}
|
<CardTitle>피드백 정보</CardTitle>
|
||||||
</span>
|
<div className="flex items-center gap-4 text-sm text-slate-500">
|
||||||
<span className="text-sm text-slate-500">
|
<span>ID: {feedback?.id}</span>
|
||||||
생성일:{" "}
|
<span>
|
||||||
{feedback?.createdAt
|
생성일:{" "}
|
||||||
? new Date(feedback.createdAt).toLocaleString("ko-KR")
|
{feedback?.createdAt
|
||||||
: "N/A"}
|
? new Date(feedback.createdAt).toLocaleString("ko-KR")
|
||||||
</span>
|
: "N/A"}
|
||||||
</div>
|
</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}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</CardHeader>
|
||||||
</div>
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import { useSyncChannelId } from "@/hooks/useSyncChannelId";
|
|||||||
import { DynamicTable } from "@/components/DynamicTable";
|
import { DynamicTable } from "@/components/DynamicTable";
|
||||||
import {
|
import {
|
||||||
getFeedbacks,
|
getFeedbacks,
|
||||||
getFeedbackSchema,
|
getFeedbackFields,
|
||||||
type Feedback,
|
type Feedback,
|
||||||
type FeedbackSchema,
|
type FeedbackField,
|
||||||
} from "@/services/feedback";
|
} from "@/services/feedback";
|
||||||
import { ErrorDisplay } from "@/components/ErrorDisplay";
|
import { ErrorDisplay } from "@/components/ErrorDisplay";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -17,7 +17,7 @@ export function FeedbackListPage() {
|
|||||||
const { projectId, channelId } = useSettingsStore();
|
const { projectId, channelId } = useSettingsStore();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [schema, setSchema] = useState<FeedbackSchema | null>(null);
|
const [schema, setSchema] = useState<FeedbackField[] | null>(null);
|
||||||
const [feedbacks, setFeedbacks] = useState<Feedback[]>([]);
|
const [feedbacks, setFeedbacks] = useState<Feedback[]>([]);
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -30,7 +30,7 @@ export function FeedbackListPage() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
const schemaData = await getFeedbackSchema(projectId, channelId);
|
const schemaData = await getFeedbackFields(projectId, channelId);
|
||||||
setSchema(schemaData);
|
setSchema(schemaData);
|
||||||
|
|
||||||
const feedbacksData = await getFeedbacks(projectId, channelId);
|
const feedbacksData = await getFeedbacks(projectId, channelId);
|
||||||
@@ -67,11 +67,13 @@ export function FeedbackListPage() {
|
|||||||
</div>
|
</div>
|
||||||
{error && <ErrorDisplay message={error} />}
|
{error && <ErrorDisplay message={error} />}
|
||||||
{schema && (
|
{schema && (
|
||||||
<DynamicTable
|
<div className="mt-6">
|
||||||
schema={schema}
|
<DynamicTable
|
||||||
data={feedbacks}
|
columns={schema}
|
||||||
onRowClick={handleRowClick}
|
data={feedbacks}
|
||||||
/>
|
onRowClick={handleRowClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -188,5 +188,11 @@ export const updateFeedback = async (
|
|||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
await handleApiError("피드백 수정에 실패했습니다.", response);
|
await handleApiError("피드백 수정에 실패했습니다.", response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const contentLength = response.headers.get("Content-Length");
|
||||||
|
if (contentLength === "0" || !contentLength) {
|
||||||
|
return {} as Feedback; // 혹은 적절한 기본 객체
|
||||||
|
}
|
||||||
|
|
||||||
return response.json();
|
return response.json();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
export const useSettingsStore = create()(
|
|
||||||
|
interface SettingsState {
|
||||||
|
projectId: string;
|
||||||
|
channelId: string;
|
||||||
|
theme: "light" | "dark" | "system";
|
||||||
|
setProjectId: (projectId: string) => void;
|
||||||
|
setChannelId: (channelId: string) => void;
|
||||||
|
setTheme: (theme: "light" | "dark" | "system") => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSettingsStore = create<SettingsState>()(
|
||||||
persist(
|
persist(
|
||||||
(set) => ({
|
(set) => ({
|
||||||
projectId: "1", // 기본 프로젝트 ID를 1로 설정
|
projectId: "1", // 기본 프로젝트 ID
|
||||||
|
channelId: "4", // 기본 채널 ID
|
||||||
theme: "system",
|
theme: "system",
|
||||||
setProjectId: (projectId) => set({ projectId }),
|
setProjectId: (projectId) => set({ projectId }),
|
||||||
|
setChannelId: (channelId) => set({ channelId }),
|
||||||
setTheme: (theme) => set({ theme }),
|
setTheme: (theme) => set({ theme }),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
@@ -13,3 +25,4 @@ export const useSettingsStore = create()(
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
|
"allowJs": false,
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
@@ -25,5 +26,5 @@
|
|||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src", "effort_js/src/services/error.js", "effort_js/src/services/feedback.js", "effort_js/src/services/issue.js", "effort_js/src/services/project.js"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export default defineConfig(({ mode }) => {
|
|||||||
alias: {
|
alias: {
|
||||||
"@": path.resolve(__dirname, "./src"),
|
"@": path.resolve(__dirname, "./src"),
|
||||||
},
|
},
|
||||||
|
extensions: [".ts", ".tsx", ".mjs", ".mts", ".json"],
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
|
|||||||
Reference in New Issue
Block a user