diff --git a/.vscode/settings.json b/.vscode/settings.json
index 9a2eadf..686b150 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -4,6 +4,6 @@
"editor.formatOnSave": true,
"eslint.format.enable": true,
"editor.codeActionsOnSave": {
- "source.fixAll.eslint": true
+ "source.fixAll.eslint": "explicit"
}
}
diff --git a/middleware.ts b/middleware.ts
index cf149c2..358761c 100644
--- a/middleware.ts
+++ b/middleware.ts
@@ -38,15 +38,15 @@ export function middleware(request: NextRequest): NextResponse {
return NextResponse.next();
}
-export const config = {
- matcher: [
- MYDASHBOARD_PATH,
- `${MYDASHBOARD_PATH}/:path*`,
- '/mypage',
- '/mypage/:path*',
- DASHBOARD_PATH,
- `${DASHBOARD_PATH}/:path*`,
- LOGIN_PATH,
- '/signup',
- ],
-};
+// export const config = {
+// matcher: [
+// MYDASHBOARD_PATH,
+// `${MYDASHBOARD_PATH}/:path*`,
+// '/mypage',
+// '/mypage/:path*',
+// DASHBOARD_PATH,
+// `${DASHBOARD_PATH}/:path*`,
+// LOGIN_PATH,
+// '/signup',
+// ],
+// };
diff --git a/src/components/dashboard/add-column-button.tsx b/src/components/dashboard/add-column-button.tsx
index 0c0124d..8040429 100644
--- a/src/components/dashboard/add-column-button.tsx
+++ b/src/components/dashboard/add-column-button.tsx
@@ -1,5 +1,5 @@
import Image from 'next/image';
-import type { AddColumnButtonProps } from './type';
+import type { AddColumnButtonProps } from '@/components/dashboard/type';
export default function AddColumnButton({ onClick }: AddColumnButtonProps) {
return (
diff --git a/src/components/dashboard/add-task-button.tsx b/src/components/dashboard/add-task-button.tsx
index 0444db6..8e62fe1 100644
--- a/src/components/dashboard/add-task-button.tsx
+++ b/src/components/dashboard/add-task-button.tsx
@@ -1,5 +1,5 @@
import Image from 'next/image';
-import type { AddTaskButtonProps } from './type';
+import type { AddTaskButtonProps } from '@/components/dashboard/type';
export default function AddTaskButton({ onClick }: AddTaskButtonProps) {
return (
diff --git a/src/components/dashboard/column-header.tsx b/src/components/dashboard/column-header.tsx
index a40504e..30fba0e 100644
--- a/src/components/dashboard/column-header.tsx
+++ b/src/components/dashboard/column-header.tsx
@@ -1,5 +1,5 @@
import Image from 'next/image';
-import type { ColumnHeaderProps } from './type';
+import type { ColumnHeaderProps } from '@/components/dashboard/type';
export default function ColumnHeader({
column,
diff --git a/src/components/dashboard/column-layout.tsx b/src/components/dashboard/column-layout.tsx
index a544056..6626c29 100644
--- a/src/components/dashboard/column-layout.tsx
+++ b/src/components/dashboard/column-layout.tsx
@@ -1,6 +1,6 @@
-import AddColumnButton from './add-column-button';
-import DashboardColumn from './dashboard-column';
-import type { ColumnType, TaskType } from './type';
+import AddColumnButton from '@/components/dashboard/add-column-button';
+import DashboardColumn from '@/components/dashboard/dashboard-column';
+import type { ColumnType, TaskType } from '@/components/dashboard/type';
interface ColumnLayoutProps {
columns: ColumnType[];
diff --git a/src/components/dashboard/column-task-card.tsx b/src/components/dashboard/column-task-card.tsx
index 1c2b53b..89bcf2b 100644
--- a/src/components/dashboard/column-task-card.tsx
+++ b/src/components/dashboard/column-task-card.tsx
@@ -1,11 +1,13 @@
import Image from 'next/image';
+import type { TaskCardProps } from '@/components/dashboard/type';
import ChipProfile from '@/components/ui/chip/chip-profile';
import ChipTag from '@/components/ui/chip/chip-tag';
import { getProfileColor } from '@/utils/profile-color';
-import type { TaskCardProps } from './type';
-const formatDueDate = (dueDate: string) => {
- if (!dueDate) {return '';}
+const formatDueDate = (dueDate: string | undefined) => {
+ if (!dueDate) {
+ return '';
+ }
const date = new Date(dueDate);
const year = date.getFullYear();
@@ -14,7 +16,7 @@ const formatDueDate = (dueDate: string) => {
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
- return `${year}-${month}-${day} ${hours}:${minutes}`;
+ return `${String(year)}-${month}-${day} ${hours}:${minutes}`;
};
export default function ColumnTaskCard({ task, onEditTask }: TaskCardProps) {
@@ -25,7 +27,7 @@ export default function ColumnTaskCard({ task, onEditTask }: TaskCardProps) {
};
return (
-
@@ -45,20 +47,24 @@ export default function ColumnTaskCard({ task, onEditTask }: TaskCardProps) {
{/* 본문 */}
-
+
{task.title}
{/* 태그들 */}
- {task.tags.map((tag) =>
- { return }
- )}
+ {task.tags.map((tag) => {
+ return (
+
+ );
+ })}
{/* 날짜와 담당자 */}
@@ -75,7 +81,7 @@ export default function ColumnTaskCard({ task, onEditTask }: TaskCardProps) {
@@ -83,6 +89,6 @@ export default function ColumnTaskCard({ task, onEditTask }: TaskCardProps) {
-
+
);
}
diff --git a/src/components/dashboard/dashboard-column.tsx b/src/components/dashboard/dashboard-column.tsx
index 95488bf..bf6fb47 100644
--- a/src/components/dashboard/dashboard-column.tsx
+++ b/src/components/dashboard/dashboard-column.tsx
@@ -1,7 +1,7 @@
-import AddTaskButton from './add-task-button';
-import ColumnHeader from './column-header';
-import ColumnTaskCard from './column-task-card';
-import type { ColumnType, TaskType } from './type';
+import AddTaskButton from '@/components/dashboard/add-task-button';
+import ColumnHeader from '@/components/dashboard/column-header';
+import ColumnTaskCard from '@/components/dashboard/column-task-card';
+import type { ColumnType, TaskType } from '@/components/dashboard/type';
interface ColumnProps {
column: ColumnType;
@@ -32,13 +32,15 @@ export default function DashboardColumn({
{/* 할일 보드 - 스크롤 가능한 영역 */}
- {column.tasks.map((task) =>
- { return }
- )}
+ {column.tasks.map((task) => {
+ return (
+
+ );
+ })}
diff --git a/src/components/dashboard/create-column-form.tsx b/src/components/dashboard/modal/create-column-form.tsx
similarity index 87%
rename from src/components/dashboard/create-column-form.tsx
rename to src/components/dashboard/modal/create-column-form.tsx
index af9aded..833ab2b 100644
--- a/src/components/dashboard/create-column-form.tsx
+++ b/src/components/dashboard/modal/create-column-form.tsx
@@ -1,4 +1,5 @@
-import type { CreateColumnFormData } from './type';
+import type { ReactNode } from 'react';
+import type { CreateColumnFormData } from '@/components/dashboard/type';
interface CreateColumnFormProps {
formData: CreateColumnFormData;
@@ -10,7 +11,7 @@ export default function CreateColumnForm({
formData,
setFormData,
hasError = false,
-}: CreateColumnFormProps): JSX.Element {
+}: CreateColumnFormProps): ReactNode {
return (
<>
{/* 이름 */}
diff --git a/src/components/dashboard/create-column-modal.tsx b/src/components/dashboard/modal/create-column-modal.tsx
similarity index 68%
rename from src/components/dashboard/create-column-modal.tsx
rename to src/components/dashboard/modal/create-column-modal.tsx
index ce8e6f8..c9d74bf 100644
--- a/src/components/dashboard/create-column-modal.tsx
+++ b/src/components/dashboard/modal/create-column-modal.tsx
@@ -1,8 +1,8 @@
import { useState } from 'react';
+import CreateColumnForm from '@/components/dashboard/modal/create-column-form';
+import type { CreateColumnFormData } from '@/components/dashboard/type';
+import ButtonModal from '@/components/ui/modal/modal-button';
import { useModalKeyHandler } from '@/hooks/useModal';
-import BaseModal from '../ui/base-modal';
-import CreateColumnForm from './create-column-form';
-import type { CreateColumnFormData } from './type';
interface CreateColumnModalProps {
isOpen: boolean;
@@ -37,30 +37,33 @@ export default function CreateColumnModal({
const isMaxColumnsReached = existingColumns.length >= maxColumns;
const handleSubmit = () => {
- if (!isDuplicate && !isMaxColumnsReached) {
- onSubmit(formData);
- handleClose();
+ if (isDuplicate) {
+ return;
}
+ if (isMaxColumnsReached) {
+ return;
+ }
+ onSubmit(formData);
+ handleClose();
};
const isSubmitDisabled =
!formData.name.trim() || isDuplicate || isMaxColumnsReached;
+ const errorMessage = isMaxColumnsReached
+ ? `최대 ${String(maxColumns)}개까지만 생성할 수 있습니다.`
+ : isDuplicate
+ ? '중복된 컬럼 이름입니다.'
+ : undefined;
return (
-
@@ -69,6 +72,6 @@ export default function CreateColumnModal({
setFormData={setFormData}
hasError={isDuplicate}
/>
-
+
);
}
diff --git a/src/components/dashboard/create-task-form.tsx b/src/components/dashboard/modal/create-task-form.tsx
similarity index 67%
rename from src/components/dashboard/create-task-form.tsx
rename to src/components/dashboard/modal/create-task-form.tsx
index 9d283ba..fc8f1dc 100644
--- a/src/components/dashboard/create-task-form.tsx
+++ b/src/components/dashboard/modal/create-task-form.tsx
@@ -1,13 +1,15 @@
import Image from 'next/image';
-import type React from 'react';
-import { useEffect, useRef, useState } from 'react';
+import { useState } from 'react';
+import {
+ type CreateTaskFormData,
+ getRandomTagColor,
+} from '@/components/dashboard/type';
import ChipProfile from '@/components/ui/chip/chip-profile';
import ChipTag from '@/components/ui/chip/chip-tag';
+import Dropdown from '@/components/ui/dropdown';
import { mockProfileColors } from '@/lib/dashboard-mock-data';
import type { UserType } from '@/lib/users/type';
import { getProfileColor } from '@/utils/profile-color';
-import type { CreateTaskFormData } from './type';
-import { getRandomTagColor } from './type';
interface CreateTaskFormProps {
formData: CreateTaskFormData;
@@ -26,7 +28,6 @@ export default function CreateTaskForm({
}: CreateTaskFormProps): React.ReactElement {
const [currentTag, setCurrentTag] = useState('');
const [isAssigneeDropdownOpen, setIsAssigneeDropdownOpen] = useState(false);
- const assigneeDropdownRef = useRef(null);
const assigneeOptions = [
{
@@ -45,49 +46,45 @@ export default function CreateTaskForm({
profileColor: mockProfileColors[2],
},
];
-
+ const chipProfileLabel = (
+ assigneeOptions.find((opt) => opt.value === formData.assignee)?.label || ''
+ ).slice(0, 1);
+ const chipProfileColor = getProfileColor(
+ assigneeOptions.find((opt) => opt.value === formData.assignee)
+ ?.profileColor || '#10b981'
+ );
const handleAssigneeSelect = (assignee: string) => {
setFormData((prev) => ({ ...prev, assignee }));
setIsAssigneeDropdownOpen(false);
};
- // 드롭다운 외부 클릭 시 닫기
- useEffect(() => {
- const handleClickOutside = (event: MouseEvent) => {
- if (
- assigneeDropdownRef.current &&
- !assigneeDropdownRef.current.contains(event.target as Node)
- ) {
- setIsAssigneeDropdownOpen(false);
- }
- };
-
- document.addEventListener('mousedown', handleClickOutside);
-
- return () => {
- document.removeEventListener('mousedown', handleClickOutside);
- };
- }, []);
-
const handleTagKeyDown = (e: React.KeyboardEvent) => {
- if (e.key === 'Enter' && currentTag.trim()) {
- e.preventDefault();
- setFormData((prev) => ({
+ if (e.key !== 'Enter') {
+ return;
+ }
+ if (!currentTag.trim()) {
+ return;
+ }
+ e.preventDefault();
+ setFormData((prev) => {
+ return {
...prev,
tags: [
...prev.tags,
{ label: currentTag.trim(), color: getRandomTagColor() },
],
- }));
- setCurrentTag('');
- }
+ };
+ });
+ setCurrentTag('');
};
const removeTag = (indexToRemove: number) => {
- setFormData((prev) => ({
- ...prev,
- tags: prev.tags.filter((_, index) => index !== indexToRemove),
- }));
+ setFormData((prev) => {
+ return {
+ ...prev,
+ tags: prev.tags.filter((_, index) => index !== indexToRemove),
+ };
+ });
};
const handleImageUpload = (e: React.ChangeEvent) => {
@@ -116,95 +113,74 @@ export default function CreateTaskForm({
<>
{/* 담당자 */}
-
-
-
{/* 제목 */}
@@ -283,6 +259,7 @@ export default function CreateTaskForm({
const input = document.querySelector(
'#dueDate'
) as HTMLInputElement;
+
input.showPicker();
input.focus();
}}
@@ -309,10 +286,10 @@ export default function CreateTaskForm({
{
- console.log('CreateTaskModal handleClose called');
setFormData({
assignee: '',
title: '',
@@ -37,7 +36,6 @@ export default function CreateTaskModal({
tags: [],
imageFile: null,
});
- console.log('Calling onClose');
onClose();
};
@@ -54,7 +52,7 @@ export default function CreateTaskModal({
!formData.title.trim() || !formData.description.trim();
return (
-
-
+
);
}
diff --git a/src/components/dashboard/delete-column-modal.tsx b/src/components/dashboard/modal/delete-column-modal.tsx
similarity index 87%
rename from src/components/dashboard/delete-column-modal.tsx
rename to src/components/dashboard/modal/delete-column-modal.tsx
index 184f017..cde0b3b 100644
--- a/src/components/dashboard/delete-column-modal.tsx
+++ b/src/components/dashboard/modal/delete-column-modal.tsx
@@ -1,4 +1,4 @@
-import BaseModal from '../ui/base-modal';
+import ButtonModal from '@/components/ui/modal/modal-button';
interface DeleteColumnModalProps {
isOpen: boolean;
@@ -19,7 +19,7 @@ export default function DeleteColumnModal({
};
return (
-
-
+
);
}
diff --git a/src/components/dashboard/edit-task-form.tsx b/src/components/dashboard/modal/edit-task-form.tsx
similarity index 56%
rename from src/components/dashboard/edit-task-form.tsx
rename to src/components/dashboard/modal/edit-task-form.tsx
index f0eb548..c82d8d1 100644
--- a/src/components/dashboard/edit-task-form.tsx
+++ b/src/components/dashboard/modal/edit-task-form.tsx
@@ -1,13 +1,16 @@
import Image from 'next/image';
-import { useEffect, useRef, useState } from 'react';
+import { useState } from 'react';
+import {
+ type EditTaskFormData,
+ getRandomTagColor,
+} from '@/components/dashboard/type';
import ChipProfile from '@/components/ui/chip/chip-profile';
import ChipState from '@/components/ui/chip/chip-state';
import ChipTag from '@/components/ui/chip/chip-tag';
+import Dropdown from '@/components/ui/dropdown';
import { mockProfileColors } from '@/lib/dashboard-mock-data';
import type { UserType } from '@/lib/users/type';
import { getProfileColor } from '@/utils/profile-color';
-import { type EditTaskFormData , getRandomTagColor } from './type';
-
interface EditTaskFormProps {
formData: EditTaskFormData;
@@ -27,13 +30,13 @@ export default function EditTaskForm({
const [currentTag, setCurrentTag] = useState('');
const [isStatusDropdownOpen, setIsStatusDropdownOpen] = useState(false);
const [isAssigneeDropdownOpen, setIsAssigneeDropdownOpen] = useState(false);
- const statusDropdownRef = useRef
(null);
- const assigneeDropdownRef = useRef(null);
- const statusOptions = columns.map((column) => { return {
- value: column.title,
- label: column.title,
- } });
+ const statusOptions = columns.map((column) => {
+ return {
+ value: column.title,
+ label: column.title,
+ };
+ });
const assigneeOptions = [
{
@@ -63,71 +66,57 @@ export default function EditTaskForm({
setIsAssigneeDropdownOpen(false);
};
- // 드롭다운 외부 클릭 시 닫기
- useEffect(() => {
- const handleClickOutside = (event: MouseEvent) => {
- if (
- statusDropdownRef.current &&
- !statusDropdownRef.current.contains(event.target as Node)
- ) {
- setIsStatusDropdownOpen(false);
- }
- if (
- assigneeDropdownRef.current &&
- !assigneeDropdownRef.current.contains(event.target as Node)
- ) {
- setIsAssigneeDropdownOpen(false);
- }
- };
-
- if (isStatusDropdownOpen || isAssigneeDropdownOpen) {
- document.addEventListener('mousedown', handleClickOutside);
- }
-
- return () => {
- document.removeEventListener('mousedown', handleClickOutside);
- };
- }, [isStatusDropdownOpen, isAssigneeDropdownOpen]);
-
const handleTagKeyDown = (e: React.KeyboardEvent) => {
- if (e.key === 'Enter' && currentTag.trim()) {
- e.preventDefault();
- setFormData((prev) => { return {
+ if (e.key !== 'Enter') {
+ return;
+ }
+ if (!currentTag.trim()) {
+ return;
+ }
+ e.preventDefault();
+ setFormData((prev) => {
+ return {
...prev,
tags: [
...prev.tags,
{ label: currentTag.trim(), color: getRandomTagColor() },
],
- } });
- setCurrentTag('');
- }
+ };
+ });
+ setCurrentTag('');
};
const removeTag = (indexToRemove: number) => {
- setFormData((prev) => { return {
- ...prev,
- tags: prev.tags.filter((_, index) => index !== indexToRemove),
- } });
+ setFormData((prev) => {
+ return {
+ ...prev,
+ tags: prev.tags.filter((_, index) => index !== indexToRemove),
+ };
+ });
};
const handleImageUpload = (e: React.ChangeEvent) => {
const file = e.target.files?.[0];
if (file) {
- setFormData((prev) => { return {
- ...prev,
- imageFile: file,
- existingImageUrl: undefined,
- } });
+ setFormData((prev) => {
+ return {
+ ...prev,
+ imageFile: file,
+ existingImageUrl: undefined,
+ };
+ });
}
};
const removeImage = () => {
- setFormData((prev) => { return {
- ...prev,
- imageFile: null,
- existingImageUrl: undefined,
- } });
+ setFormData((prev) => {
+ return {
+ ...prev,
+ imageFile: null,
+ existingImageUrl: undefined,
+ };
+ });
};
return (
@@ -135,146 +124,135 @@ export default function EditTaskForm({
{/* 상태 */}
-
-
-
상태
+
+ {
setIsStatusDropdownOpen(!isStatusDropdownOpen);
}}
>
-
-
-
-
- {/* 드롭다운 옵션들 */}
- {isStatusDropdownOpen && (
-
- {statusOptions.map((option, index) =>
- { return
+
+
+
+
+
+ {statusOptions.map((option) => {
+ return (
+ { handleStatusSelect(option.value); }}
+ onClick={() => {
+ handleStatusSelect(option.value);
+ }}
>
-
- {formData.status === option.value && (
-
- )}
+
+
+ {formData.status === option.value && (
+
+ )}
+
+
-
- }
- )}
-
- )}
-
+
+ );
+ })}
+
+
-
{/* 담당자 */}
-
-
+ {
setIsAssigneeDropdownOpen(!isAssigneeDropdownOpen);
}}
>
-
- {formData.assignee ? (
- <>
-
+
+ {formData.assignee ? (
+ <>
+ opt.value === formData.assignee
+ )?.label || ''
+ ).slice(0, 1) || '배'
+ }
+ color={getProfileColor(
assigneeOptions.find(
(opt) => opt.value === formData.assignee
- )?.label || ''
- ).slice(0, 1) || '배'
- }
- color={getProfileColor(
- assigneeOptions.find(
+ )?.profileColor || mockProfileColors[0]
+ )}
+ />
+
+ {assigneeOptions.find(
(opt) => opt.value === formData.assignee
- )?.profileColor || mockProfileColors[0]
- )}
- />
-
- {assigneeOptions.find(
- (opt) => opt.value === formData.assignee
- )?.label || '이름을 입력해 주세요'}
-
- >
- ) : (
- 이름을 입력해 주세요
- )}
+ )?.label || '이름을 입력해 주세요'}
+
+ >
+ ) : (
+ 이름을 입력해 주세요
+ )}
+
+
-
-
-
- {/* 드롭다운 옵션들 */}
- {isAssigneeDropdownOpen && (
-
- {assigneeOptions.map((option, index) =>
- { return
+
+ {assigneeOptions.map((option) => {
+ return (
+ { handleAssigneeSelect(option.value); }}
+ onClick={() => {
+ handleAssigneeSelect(String(option.value));
+ }}
>
-
- {formData.assignee === option.value && (
-
+
+ {formData.assignee === option.value && (
+
+ )}
+
+
+
- )}
-
-
-
- {option.label}
+ {option.label}
+
- }
- )}
-
- )}
-
+
+ );
+ })}
+
+
@@ -317,10 +295,12 @@ export default function EditTaskForm({
className='focus:border-violet w-full resize-none rounded-lg border border-gray-300 p-4 focus:outline-none'
value={formData.description}
onChange={(e) => {
- setFormData((prev) => { return {
- ...prev,
- description: e.target.value,
- } });
+ setFormData((prev) => {
+ return {
+ ...prev,
+ description: e.target.value,
+ };
+ });
}}
/>
@@ -342,18 +322,18 @@ export default function EditTaskForm({
setFormData((prev) => ({ ...prev, dueDate: e.target.value }));
}}
onClick={(e) => {
- (e.currentTarget as HTMLInputElement).showPicker?.();
+ (e.currentTarget as HTMLInputElement).showPicker();
}}
/>
- {
const input = document.querySelector(
'#dueDate'
) as HTMLInputElement;
- input?.showPicker?.();
- input?.focus();
+ input.showPicker();
+ input.focus();
}}
>
-
+
@@ -373,20 +353,25 @@ export default function EditTaskForm({
{/* 기존 태그들 */}
- {formData.tags.map((tag, index) =>
- { return
-
-
{
- removeTag(index);
- }}
+ {formData.tags.map((tag, index) => {
+ return (
+
- ×
-
-
}
- )}
+
+ {
+ removeTag(index);
+ }}
+ >
+ ×
+
+
+ );
+ })}
{/* 새 태그 입력 */}
- {formData.imageFile ? (
+ {formData.imageFile && (
- ) : formData.existingImageUrl ? (
+ )}
+ {!formData.imageFile && formData.existingImageUrl && (
- ) : null}
+ )}