중간 수정. 피드백 디테일보기 해결
This commit is contained in:
@@ -1,4 +1,10 @@
|
|||||||
# Q&A 뷰어 프로젝트
|
# Q&A 뷰어 프로젝트
|
||||||
|
## 0. 프젝트 전체 작업 진행 원칙
|
||||||
|
- GEMINI cli로 하는 모든 작업에 대해 한글로 응답하며, 기능단위 결과를 TODO.md 에 남긴다.
|
||||||
|
- 결과의 기록은 date 명령을 이용해 KST 기준으로 시분초까지 작성한다.
|
||||||
|
- node_modules를 제외한 이 프로젝트에서 생성하는 파일은 ts 혹은 tsx 이며 js 생성시
|
||||||
|
- 기능 단위 완성 후 사용자 승인을 받고 Biome를 이용해 lint 및 format 검사를 수행한다.
|
||||||
|
- 최종적으로 사용자에게 git commit을 한 뒤 TODO.md에 기록한다.
|
||||||
|
|
||||||
## 1. 프로젝트 개요
|
## 1. 프로젝트 개요
|
||||||
|
|
||||||
@@ -13,7 +19,7 @@
|
|||||||
- OIDC 표준을 이용한 사용자 인증
|
- OIDC 표준을 이용한 사용자 인증
|
||||||
|
|
||||||
## 3. 기술 스택
|
## 3. 기술 스택
|
||||||
|
- **Language**: `typescript`
|
||||||
- **Package Manager**: `pnpm`
|
- **Package Manager**: `pnpm`
|
||||||
- **Framework**: `React`
|
- **Framework**: `React`
|
||||||
- **Build Tool**: `Vite`
|
- **Build Tool**: `Vite`
|
||||||
@@ -47,7 +53,6 @@
|
|||||||
- **진행할 작업**:
|
- **진행할 작업**:
|
||||||
- **테마 커스터마이징**: Dracula 테마를 다크 모드에 적용하고, 기본 테마를 라이트 모드로 설정
|
- **테마 커스터마이징**: Dracula 테마를 다크 모드에 적용하고, 기본 테마를 라이트 모드로 설정
|
||||||
- **동적 폼 개선**: Zod와 같은 라이브러리를 활용하여 스키마 기반의 동적 데이터 유효성 검사 구현
|
- **동적 폼 개선**: Zod와 같은 라이브러리를 활용하여 스키마 기반의 동적 데이터 유효성 검사 구현
|
||||||
- **코드 품질 관리**: Biome을 프로젝트에 통합하여 코드 포맷팅과 린트 검사를 자동화하고, 일관된 코드 스타일 유지
|
|
||||||
|
|
||||||
### Phase 3: 인증 및 배포 (예정)
|
### Phase 3: 인증 및 배포 (예정)
|
||||||
|
|
||||||
|
|||||||
1
TODO.md
1
TODO.md
@@ -20,7 +20,6 @@
|
|||||||
- [x] Light/Dark/System 테마 기능 구현 및 커스텀 테마 적용 (완료: 2025-07-31 17:20:41)
|
- [x] Light/Dark/System 테마 기능 구현 및 커스텀 테마 적용 (완료: 2025-07-31 17:20:41)
|
||||||
- [x] TypeScript 및 빌드 오류 디버깅 및 해결 (완료: 2025-07-31 17:20:41)
|
- [x] TypeScript 및 빌드 오류 디버깅 및 해결 (완료: 2025-07-31 17:20:41)
|
||||||
- [ ] 동적 폼에 데이터 유효성 검사 기능 추가
|
- [ ] 동적 폼에 데이터 유효성 검사 기능 추가
|
||||||
- [ ] Biome을 이용한 코드 포맷팅 및 린트 규칙 적용 및 검사
|
|
||||||
|
|
||||||
## Phase 3: 인증 및 배포
|
## Phase 3: 인증 및 배포
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ interface DynamicFormProps {
|
|||||||
onSubmit: (formData: Record<string, unknown>) => Promise<void>;
|
onSubmit: (formData: Record<string, unknown>) => Promise<void>;
|
||||||
initialData?: Record<string, unknown>;
|
initialData?: Record<string, unknown>;
|
||||||
submitButtonText?: string;
|
submitButtonText?: string;
|
||||||
|
onCancel?: () => void;
|
||||||
|
cancelButtonText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DynamicForm({
|
export function DynamicForm({
|
||||||
@@ -26,6 +28,8 @@ export function DynamicForm({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
initialData = EMPTY_INITIAL_DATA, // 기본값으로 상수 사용
|
initialData = EMPTY_INITIAL_DATA, // 기본값으로 상수 사용
|
||||||
submitButtonText = "제출",
|
submitButtonText = "제출",
|
||||||
|
onCancel,
|
||||||
|
cancelButtonText = "취소",
|
||||||
}: DynamicFormProps) {
|
}: DynamicFormProps) {
|
||||||
const [formData, setFormData] =
|
const [formData, setFormData] =
|
||||||
useState<Record<string, unknown>>(initialData);
|
useState<Record<string, unknown>>(initialData);
|
||||||
@@ -107,9 +111,16 @@ export function DynamicForm({
|
|||||||
{renderField(field)}
|
{renderField(field)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<Button type="submit" disabled={isSubmitting}>
|
<div className="flex justify-between">
|
||||||
{isSubmitting ? "전송 중..." : submitButtonText}
|
<Button type="submit" disabled={isSubmitting}>
|
||||||
</Button>
|
{isSubmitting ? "전송 중..." : submitButtonText}
|
||||||
|
</Button>
|
||||||
|
{onCancel && (
|
||||||
|
<Button type="button" variant="outline" onClick={onCancel}>
|
||||||
|
{cancelButtonText}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ import { useState, useEffect, useMemo } from "react";
|
|||||||
import { useParams, useNavigate } from "react-router-dom";
|
import { useParams, useNavigate } from "react-router-dom";
|
||||||
import { DynamicForm } from "@/components/DynamicForm";
|
import { DynamicForm } from "@/components/DynamicForm";
|
||||||
import { useSyncChannelId } from "@/hooks/useSyncChannelId";
|
import { useSyncChannelId } from "@/hooks/useSyncChannelId";
|
||||||
import { getFeedbackById, updateFeedback } from "@/services/feedback";
|
import {
|
||||||
|
getFeedbackById,
|
||||||
|
updateFeedback,
|
||||||
|
getFeedbackFields,
|
||||||
|
} from "@/services/feedback";
|
||||||
import { ErrorDisplay } from "@/components/ErrorDisplay";
|
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";
|
import { Separator } from "@/components/ui/separator";
|
||||||
@@ -147,6 +151,9 @@ export function FeedbackDetailPage() {
|
|||||||
initialData={initialData}
|
initialData={initialData}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
submitButtonText="수정하기"
|
submitButtonText="수정하기"
|
||||||
|
onCancel={() =>
|
||||||
|
navigate(`/projects/${projectId}/channels/${channelId}/feedbacks`)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
{successMessage && (
|
{successMessage && (
|
||||||
<div className="mt-4 p-3 bg-green-100 text-green-800 rounded-md">
|
<div className="mt-4 p-3 bg-green-100 text-green-800 rounded-md">
|
||||||
|
|||||||
Reference in New Issue
Block a user