-
+
setIsCalendarVisible((prev) => !prev)}
/>
+
{isCalendarVisible && (
-
- {daysInMonth.map((date, index) => (
- date && handleDateClick(date)}
- disabled={!date}
- >
- {date?.getDate() || ''}
-
- ))}
+ {daysInMonth.map((date, index) => {
+ const now = new Date();
+ const todayStart = new Date(now.setHours(0, 0, 0, 0));
+ const isToday = date?.getTime() === todayStart.getTime();
+ const isPastDate =
+ (date?.getTime() ?? Infinity) < todayStart.getTime() ||
+ (isToday && now.getHours() >= 23 && now.getMinutes() >= 30);
+
+ return (
+ date && handleDateClick(date)}
+ disabled={!date || isPastDate}
+ >
+ {date?.getDate() || ''}
+
+ );
+ })}
-
+
)}
);
-};
-
-export default DatePicker;
+}
diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/SearchDropdown.module.css b/src/app/(with-header-sidebar)/dashboard/[id]/components/SearchDropdown.module.css
index f5f16a5..d22842d 100644
--- a/src/app/(with-header-sidebar)/dashboard/[id]/components/SearchDropdown.module.css
+++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/SearchDropdown.module.css
@@ -3,7 +3,7 @@
flex-direction: column;
gap: 8px;
position: relative;
- width: 287px;
+ width: 295px;
}
.label {
diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/SearchDropdown.tsx b/src/app/(with-header-sidebar)/dashboard/[id]/components/SearchDropdown.tsx
index 79aa1f0..80a3d35 100644
--- a/src/app/(with-header-sidebar)/dashboard/[id]/components/SearchDropdown.tsx
+++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/SearchDropdown.tsx
@@ -1,44 +1,44 @@
'use client';
-import { useState, useRef, useEffect } from 'react';
-import styles from './SearchDropdown.module.css';
+import { useState, useRef, useEffect, KeyboardEvent } from 'react';
import Image from 'next/image';
-import { ManagerOption } from './CreateTaskModal';
-import CheckIcon from '/public/icons/done.svg';
import Avatar from '@/components/Avatar';
+import { Member } from '@/types/member';
+import styles from './SearchDropdown.module.css';
+import CheckIcon from '/public/icons/done.svg';
interface SearchDropdownProps {
- options: ManagerOption[];
+ options: Member[];
+ name: 'assigneeUserId';
+ setValue: (name: 'assigneeUserId', value: number) => void;
placeholder?: string;
- onSelect: (selected: ManagerOption) => void;
}
export default function SearchDropdown({
options,
+ name,
+ setValue,
placeholder = '이름을 입력해 주세요',
- onSelect,
}: SearchDropdownProps) {
const [query, setQuery] = useState('');
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
const [focusedIndex, setFocusedIndex] = useState(-1);
- const [selectedOption, setSelectedOption] = useState
(
- null
- );
+ const [selectedOption, setSelectedOption] = useState(null);
const dropdownRef = useRef(null);
const filteredOptions = options.filter((option) =>
- option.name.toLowerCase().includes(query.toLowerCase())
+ option.nickname.toLowerCase().includes(query.toLowerCase())
);
- const handleSelect = (option: ManagerOption) => {
- setQuery(option.name);
+ const handleSelect = (option: Member) => {
+ setQuery(option.nickname);
setSelectedOption(option);
setIsDropdownVisible(false);
- onSelect(option);
+ setValue(name, option.userId);
setFocusedIndex(-1);
};
- const handleKeyDown = (e: React.KeyboardEvent) => {
+ const handleKeyDown = (e: KeyboardEvent) => {
if (!isDropdownVisible && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) {
setIsDropdownVisible(true);
return;
@@ -84,7 +84,7 @@ export default function SearchDropdown({
if (!dropdownRef.current?.contains(e.relatedTarget as Node)) {
setIsDropdownVisible(false);
setFocusedIndex(-1);
- setQuery(selectedOption?.name || '');
+ setQuery(selectedOption?.nickname || '');
}
};
@@ -101,10 +101,10 @@ export default function SearchDropdown({
option,
index,
}: {
- option: ManagerOption;
+ option: Member;
index: number;
}) => {
- const isSelected = selectedOption?.id === option.id;
+ const isSelected = selectedOption?.userId === option.userId;
const isFocused = index === focusedIndex;
return (
@@ -117,8 +117,12 @@ export default function SearchDropdown({
aria-selected={isSelected}
>
{isSelected && }
-
- {option.name}
+
+ {option.nickname}
);
};
@@ -132,8 +136,9 @@ export default function SearchDropdown({
{
@@ -141,10 +146,15 @@ export default function SearchDropdown({
setIsDropdownVisible(true);
}}
onFocus={() => setIsDropdownVisible(true)}
+ autoComplete="off"
/>
- {selectedOption?.name === query && (
+ {selectedOption?.nickname === query && (
)}
{filteredOptions.length > 0 ? (
filteredOptions.map((option, index) => (
-
+
))
) : (
검색 결과가 없습니다
diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/edit/_hooks/useMember.ts b/src/app/(with-header-sidebar)/dashboard/[id]/edit/_hooks/useMember.ts
index a06fbd3..2180867 100644
--- a/src/app/(with-header-sidebar)/dashboard/[id]/edit/_hooks/useMember.ts
+++ b/src/app/(with-header-sidebar)/dashboard/[id]/edit/_hooks/useMember.ts
@@ -31,7 +31,8 @@ const useMember = (dashboardId: string | null, pageSize = 4) => {
try {
const response: GetMembersResponse | undefined = await getMembersAsync(
dashboardId,
- page
+ page,
+ pageSize
);
const members = response?.members ?? [];
diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/edit/_lib/memberService.ts b/src/app/(with-header-sidebar)/dashboard/[id]/edit/_lib/memberService.ts
index 7878b63..b6b685c 100644
--- a/src/app/(with-header-sidebar)/dashboard/[id]/edit/_lib/memberService.ts
+++ b/src/app/(with-header-sidebar)/dashboard/[id]/edit/_lib/memberService.ts
@@ -1,15 +1,14 @@
import axiosInstance from '@/lib/axiosInstance';
import { GetMembersResponse } from '@/types/member';
-const PAGE_SIZE = 4;
-
export const getMembers = async (
dashboardId: string,
- page: number = 1
+ page: number = 1,
+ pageSize: number = 4
): Promise => {
try {
const response = await axiosInstance.get(
- `members?page=${page}&size=${PAGE_SIZE}&dashboardId=${dashboardId}`
+ `members?page=${page}&size=${pageSize}&dashboardId=${dashboardId}`
);
return response.data;
} catch (error) {
diff --git a/src/app/(with-header-sidebar)/mypage/_components/Label.module.css b/src/app/(with-header-sidebar)/mypage/_components/Label.module.css
deleted file mode 100644
index d8a06fa..0000000
--- a/src/app/(with-header-sidebar)/mypage/_components/Label.module.css
+++ /dev/null
@@ -1,13 +0,0 @@
-.label {
- font-size: 14px;
- font-weight: 400;
- line-height: 24px;
- color: var(--black-100);
-}
-
-@media screen and (min-width: 768px) {
- .label {
- font-size: 16px;
- line-height: 26px;
- }
-}
diff --git a/src/app/(with-header-sidebar)/mypage/_components/Label.tsx b/src/app/(with-header-sidebar)/mypage/_components/Label.tsx
deleted file mode 100644
index 95065af..0000000
--- a/src/app/(with-header-sidebar)/mypage/_components/Label.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { ReactNode } from 'react';
-import styles from './Label.module.css';
-
-interface LabelProps {
- htmlFor: string;
- children?: ReactNode;
-}
-
-export default function Label({ htmlFor, children }: LabelProps) {
- return (
-
- );
-}
diff --git a/src/app/(with-header-sidebar)/mypage/_components/Modal.module.css b/src/app/(with-header-sidebar)/mypage/_components/Modal.module.css
index 79dc9cd..e43ee12 100644
--- a/src/app/(with-header-sidebar)/mypage/_components/Modal.module.css
+++ b/src/app/(with-header-sidebar)/mypage/_components/Modal.module.css
@@ -12,6 +12,9 @@
}
.modal {
+ max-height: 95%;
+ overflow-y: auto;
+ scrollbar-width: none;
background: var(--white);
border-radius: 16px;
}
diff --git a/src/app/(with-header-sidebar)/mypage/_components/ProfileForm.tsx b/src/app/(with-header-sidebar)/mypage/_components/ProfileForm.tsx
index 09096d4..1fc517f 100644
--- a/src/app/(with-header-sidebar)/mypage/_components/ProfileForm.tsx
+++ b/src/app/(with-header-sidebar)/mypage/_components/ProfileForm.tsx
@@ -3,7 +3,7 @@
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import useAuth from '../_hooks/useAuth';
-import FileInput from './FileInput';
+import FileInput from '@/components/FileInput';
import Input from '@/components/Input';
import Button from '@/components/Button';
import { updateProfile } from '../_lib/userService';
diff --git a/src/app/(with-header-sidebar)/mypage/_components/FileInput.module.css b/src/components/FileInput.module.css
similarity index 70%
rename from src/app/(with-header-sidebar)/mypage/_components/FileInput.module.css
rename to src/components/FileInput.module.css
index 767db80..a2d2f19 100644
--- a/src/app/(with-header-sidebar)/mypage/_components/FileInput.module.css
+++ b/src/components/FileInput.module.css
@@ -1,4 +1,13 @@
.label {
+ display: block;
+ font-size: 14px;
+ font-weight: 500;
+ line-height: 24px;
+ color: var(--black-100);
+ margin-bottom: 5px;
+}
+
+.wrapper {
display: flex;
justify-content: center;
align-items: center;
@@ -10,7 +19,7 @@
position: relative;
}
-.iconContainer {
+.iconContainer.iconContainer {
position: relative;
width: 20px;
height: 20px;
@@ -29,12 +38,12 @@
transition: opacity 0.3s ease-in-out;
}
-.label:hover .image {
+.wrapper:hover .image {
filter: brightness(50%);
transition: filter 0.3s ease-in-out;
}
-.label:hover .hoverContent {
+.wrapper:hover .hoverContent {
opacity: 1;
transition: opacity 0.3s ease-in-out;
z-index: 1;
@@ -42,9 +51,15 @@
@media screen and (min-width: 768px) {
.label {
+ font-size: 18px;
+ line-height: 26px;
+ }
+
+ .wrapper {
width: 182px;
height: 182px;
}
+
.imageContainer {
width: 30px;
height: 30px;
diff --git a/src/app/(with-header-sidebar)/mypage/_components/FileInput.tsx b/src/components/FileInput.tsx
similarity index 76%
rename from src/app/(with-header-sidebar)/mypage/_components/FileInput.tsx
rename to src/components/FileInput.tsx
index 9bd3793..01ae52d 100644
--- a/src/app/(with-header-sidebar)/mypage/_components/FileInput.tsx
+++ b/src/components/FileInput.tsx
@@ -10,14 +10,25 @@ interface FileInputProps {
name: 'image';
setValue: (name: 'image', value: File | null) => void;
url?: string | null;
+ className?: string;
+ label?: string;
}
-export default function FileInput({ name, setValue, url, id }: FileInputProps) {
+export default function FileInput({
+ name,
+ setValue,
+ url,
+ id,
+ className,
+ label,
+}: FileInputProps) {
const [preview, setPreview] = useState(null);
const handleChange = (event: ChangeEvent) => {
const file = event.target.files?.[0];
if (file) {
+ const allowedTypes = ['image/png', 'image/jpeg'];
+ if (!allowedTypes.includes(file.type)) return;
setPreview(URL.createObjectURL(file));
setValue(name, file);
}
@@ -30,8 +41,9 @@ export default function FileInput({ name, setValue, url, id }: FileInputProps) {
}, [url]);
return (
- <>
-