Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ const nextConfig: NextConfig = {
buildActivity: false,
buildActivityPosition: 'bottom-right',
},
eslint: {
ignoreDuringBuilds: true,
},
};

export default nextConfig;
1 change: 0 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions public/dashboard/check-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/dashboard/add-task-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { AddTaskButtonProps } from './type';
export default function AddTaskButton({ onClick }: AddTaskButtonProps) {
return (
<button
className='flex h-11 w-full cursor-pointer items-center justify-center gap-3 rounded-lg border border-gray-300 bg-white'
className='flex h-11 w-full flex-shrink-0 cursor-pointer items-center justify-center gap-3 rounded-lg border border-gray-300 bg-white'
onClick={onClick}
>
<Image
Expand Down
9 changes: 6 additions & 3 deletions src/components/dashboard/column-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ export default function ColumnHeader({
onSettingsClick,
}: ColumnHeaderProps) {
return (
<header className='flex items-center justify-between'>
<header className='flex flex-shrink-0 items-center justify-between'>
<div className='flex items-center gap-3'>
<div className='h-2 w-2 rounded-full bg-violet-500'></div>
<h2 className='text-lg font-bold text-gray-800'>{column.title}</h2>
<span className='flex min-w-[24px] items-center justify-center rounded-md bg-gray-200 px-2 py-1 text-center text-sm font-medium text-gray-500'>
<span className='bg-gray-4 text-gray-1 flex size-[20px] items-center justify-center rounded-[6px] text-xs'>
{column.tasks.length}
</span>
</div>
<button className='cursor-pointer rounded p-1' onClick={onSettingsClick}>
<button
className='cursor-pointer rounded p-1 transition-transform duration-200 active:rotate-90'
onClick={onSettingsClick}
>
<Image
src='/dashboard/column-setting-icon.svg'
alt='컬럼 설정'
Expand Down
14 changes: 9 additions & 5 deletions src/components/dashboard/column-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface ColumnLayoutProps {
onColumnSettingsClick?: (columnId: string) => void;
onAddTaskClick?: (columnId: string) => void;
onTaskClick?: (task: TaskType) => void;
maxColumns?: number;
}

export default function ColumnLayout({
Expand All @@ -16,6 +17,7 @@ export default function ColumnLayout({
onColumnSettingsClick,
onAddTaskClick,
onTaskClick,
maxColumns = 10,
}: ColumnLayoutProps) {
return (
<div className='column-layout-container flex h-full'>
Expand All @@ -36,12 +38,14 @@ export default function ColumnLayout({
);
})}

{/* 칼럼 추가 버튼 */}
<div className='add-column-container h-full w-80 flex-shrink-0 px-4 py-6'>
<div className='mt-12'>
<AddColumnButton onClick={onAddColumnClick} />
{/* 칼럼 추가 버튼 (최대 개수 미달 시에만 표시) */}
{columns.length < maxColumns && (
<div className='add-column-container h-full w-80 flex-shrink-0 px-4 py-6'>
<div className='mt-12'>
<AddColumnButton onClick={onAddColumnClick} />
</div>
</div>
</div>
)}
</div>
);
}
123 changes: 57 additions & 66 deletions src/components/dashboard/column-task-card.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,20 @@
import Image from 'next/image';
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 getTagColorClasses = (color: string) => {
switch (color) {
case 'orange': {
return 'bg-orange-100 text-orange-600';
}
case 'green': {
return 'bg-green-100 text-green-600';
}
case 'blue': {
return 'bg-blue-100 text-blue-600';
}
case 'red': {
return 'bg-red-100 text-red-600';
}
case 'purple': {
return 'bg-purple-100 text-purple-600';
}
default: {
return 'bg-gray-100 text-gray-600';
}
}
const formatDueDate = (dueDate: string) => {
if (!dueDate) {return '';}

const date = new Date(dueDate);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');

return `${year}-${month}-${day} ${hours}:${minutes}`;
};

export default function ColumnTaskCard({ task, onEditTask }: TaskCardProps) {
Expand All @@ -33,61 +26,59 @@ export default function ColumnTaskCard({ task, onEditTask }: TaskCardProps) {

return (
<div
className='cursor-pointer rounded-lg border border-gray-300 bg-white'
className='flex cursor-pointer flex-col gap-4 rounded-lg border border-gray-300 bg-white p-4'
onClick={handleCardClick}
>
{/* 이미지 */}
{/* 썸네일 */}
{task.imageUrl && (
<div className='p-4 pb-0'>
<div className='h-40 w-full overflow-hidden rounded-lg'>
<Image
src={task.imageUrl}
alt='카드 이미지'
width={300}
height={160}
className='h-full w-full object-cover'
/>
</div>
<div className='h-40 w-full overflow-hidden rounded-lg'>
<Image
src={task.imageUrl}
alt='카드 이미지'
width={300}
height={160}
className='h-full w-full object-cover'
/>
</div>
)}

{/* 본문 */}
<div className='p-4'>
<h3 className='mb-3 line-clamp-2 text-base font-medium text-gray-900'>
{task.title}
</h3>

{/* 태그들 */}
<div className='mb-3 flex flex-wrap gap-1.5'>
{task.tags.map((tag) =>
{ return <span
key={tag.label}
className={`rounded-md px-2 py-1 text-xs font-medium ${getTagColorClasses(tag.color)}`}
>
{tag.label}
</span> }
)}
</div>
<div className='flex flex-col justify-between'>
<div>
<h3 className='mb-3 line-clamp-2 text-base font-medium text-gray-900'>
{task.title}
</h3>

<div className='flex items-center justify-between'>
{/* 날짜 */}
<div className='flex items-center gap-1.5 text-xs text-gray-500'>
<Image
src='/dashboard/calendar.svg'
alt='달력'
width={14}
height={16}
/>
<span>{task.dueDate}</span>
{/* 태그들 */}
<div className='mb-3 flex flex-wrap items-center gap-1.5'>
{task.tags.map((tag) =>
{ return <ChipTag
key={tag.label}
label={tag.label}
color={tag.color as 'blue' | 'pink' | 'green' | 'brown' | 'red'}
size='md'
/> }
)}
</div>

{/* 담당자 */}
<div className='flex items-center'>
<div
className='flex h-6 w-6 items-center justify-center rounded-full text-xs font-medium text-white'
style={{ backgroundColor: task.manager.profileColor }}
>
{task.manager.nickname.slice(0, 2)}
{/* 날짜와 담당자 */}
<div className='flex items-center justify-between'>
<div className='flex items-center gap-1.5 text-xs text-gray-500'>
<Image
src='/dashboard/calendar.svg'
alt='달력'
width={14}
height={16}
/>
<span>{formatDueDate(task.dueDate)}</span>
</div>

<div className='flex items-center'>
<ChipProfile
label={String(task.manager.nickname || '').slice(0, 1)}
color={getProfileColor(task.manager.profileColor)}
size='sm'
/>
</div>
</div>
</div>
Expand Down
12 changes: 9 additions & 3 deletions src/components/dashboard/create-column-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import type { CreateColumnFormData } from './type';
interface CreateColumnFormProps {
formData: CreateColumnFormData;
setFormData: React.Dispatch<React.SetStateAction<CreateColumnFormData>>;
hasError?: boolean;
}

export default function CreateColumnForm({
formData,
setFormData,
}: CreateColumnFormProps) {
hasError = false,
}: CreateColumnFormProps): JSX.Element {
return (
<>
{/* 이름 */}
Expand All @@ -18,16 +20,20 @@ export default function CreateColumnForm({
className='mb-2 block text-lg leading-6 font-medium'
>
대시보드 이름
<span className='align-baseline text-lg text-indigo-600'>*</span>
<span className='text-violet align-baseline text-lg'> *</span>
</label>
<input
required
id='new-column-name'
name='name'
type='text'
placeholder='새로운 프로젝트'
className='w-full rounded-lg border border-gray-300 p-4 focus:outline-none'
value={formData.name}
className={`w-full rounded-lg border p-4 focus:outline-none ${
hasError
? 'border-red-500 focus:border-red-500'
: 'focus:border-violet border-gray-300'
}`}
onChange={(e) => {
setFormData((prev) => ({ ...prev, name: e.target.value }));
}}
Expand Down
32 changes: 28 additions & 4 deletions src/components/dashboard/create-column-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ interface CreateColumnModalProps {
isOpen: boolean;
onClose: () => void;
onSubmit: (columnData: CreateColumnFormData) => void;
existingColumns?: string[];
maxColumns?: number;
}

export default function CreateColumnModal({
isOpen,
onClose,
onSubmit,
existingColumns = [],
maxColumns = 10,
}: CreateColumnModalProps) {
const [formData, setFormData] = useState<CreateColumnFormData>({
name: '',
Expand All @@ -26,12 +30,21 @@ export default function CreateColumnModal({

useModalKeyHandler(isOpen, handleClose);

const isDuplicate = existingColumns.some(
(columnName) => columnName.toLowerCase() === formData.name.toLowerCase()
);

const isMaxColumnsReached = existingColumns.length >= maxColumns;

const handleSubmit = () => {
onSubmit(formData);
handleClose();
if (!isDuplicate && !isMaxColumnsReached) {
onSubmit(formData);
handleClose();
}
};

const isSubmitDisabled = !formData.name.trim();
const isSubmitDisabled =
!formData.name.trim() || isDuplicate || isMaxColumnsReached;

return (
<BaseModal
Expand All @@ -41,10 +54,21 @@ export default function CreateColumnModal({
cancelText='취소'
isSubmitDisabled={isSubmitDisabled}
width='w-[32rem]'
errorMessage={
isMaxColumnsReached
? `최대 ${maxColumns}개까지만 생성할 수 있습니다.`
: isDuplicate
? '중복된 컬럼 이름입니다.'
: undefined
}
onClose={handleClose}
onSubmit={handleSubmit}
>
<CreateColumnForm formData={formData} setFormData={setFormData} />
<CreateColumnForm
formData={formData}
setFormData={setFormData}
hasError={isDuplicate}
/>
</BaseModal>
);
}
Loading