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,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`;

View File

@@ -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

View File

@@ -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}

View File

@@ -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>

View File

@@ -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="제출하기"
/> />

View File

@@ -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>
); );
} }

View File

@@ -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>
); );

View File

@@ -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();
}; };

View File

@@ -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()(
}, },
), ),
); );

View File

@@ -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"]
} }

View File

@@ -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: {