diff --git a/index.html b/index.html index cc8eadc..92ffa4c 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + scheduler diff --git a/package-lock.json b/package-lock.json index 8047a23..08f71ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "scheduler", "version": "0.0.0", "dependencies": { - "@baekyangdan/core-utils": "^1.0.21", + "@baekyangdan/core-utils": "^1.0.23", "@diceui/mention": "^0.8.0", "@hookform/resolvers": "^5.2.2", "@radix-ui/react-accordion": "^1.2.12", @@ -57,6 +57,7 @@ "react-resizable-panels": "^3.0.6", "react-router-dom": "^7.9.5", "recharts": "^2.15.4", + "reflect-metadata": "^0.2.2", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", "vaul": "^1.1.2", @@ -375,9 +376,9 @@ } }, "node_modules/@baekyangdan/core-utils": { - "version": "1.0.21", - "resolved": "https://gitea.bkdhome.p-e.kr/api/packages/baekyangdan/npm/%40baekyangdan%2Fcore-utils/-/1.0.21/core-utils-1.0.21.tgz", - "integrity": "sha512-LYkzavYnforDtXm/icOg6rQkRAQAgpdwlC6w8dpWAz/N7ynIHdUHJZSPRRsQc9Jy3hQp7+vtKjZI4LP3NKC0UA==", + "version": "1.0.23", + "resolved": "https://gitea.bkdhome.p-e.kr/api/packages/baekyangdan/npm/%40baekyangdan%2Fcore-utils/-/1.0.23/core-utils-1.0.23.tgz", + "integrity": "sha512-PmxaMqMOpLKiUD5+gmC/uslSpcGzGWzaIVCTJgsHnucsPvGY+cyrUFldJaeCsJ5UeCy8eZf1hVWF3DdbKFCCVA==", "license": "ISC", "dependencies": { "@swc/core": "^1.15.5", diff --git a/package.json b/package.json index e389b62..d7bc4aa 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "preview": "vite preview" }, "dependencies": { - "@baekyangdan/core-utils": "^1.0.21", + "@baekyangdan/core-utils": "^1.0.23", "@diceui/mention": "^0.8.0", "@hookform/resolvers": "^5.2.2", "@radix-ui/react-accordion": "^1.2.12", @@ -60,6 +60,7 @@ "react-resizable-panels": "^3.0.6", "react-router-dom": "^7.9.5", "recharts": "^2.15.4", + "reflect-metadata": "^0.2.2", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", "vaul": "^1.1.2", diff --git a/public/scheduler_favicon__1_.svg b/public/scheduler_favicon__1_.svg new file mode 100644 index 0000000..978b86e --- /dev/null +++ b/public/scheduler_favicon__1_.svg @@ -0,0 +1,673 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App.tsx b/src/App.tsx index f229a25..084fcb5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,5 @@ import './App.css'; +import 'reflect-metadata'; import SignUpPage from './ui/page/account/signup/SignUpPage'; import Layout from './layouts/Layout'; import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; diff --git a/src/data/form/schedule/createSchedule.schema.ts b/src/data/form/schedule/createSchedule.schema.ts index 5c2d1fd..2539f79 100644 --- a/src/data/form/schedule/createSchedule.schema.ts +++ b/src/data/form/schedule/createSchedule.schema.ts @@ -22,6 +22,5 @@ export const CreateScheduleSchema = z.object({ .string() , dayList: z .string() - , participantList: z - .array(z.string()) + .optional() }); \ No newline at end of file diff --git a/src/data/form/schedule/updateSchedule.schema.ts b/src/data/form/schedule/updateSchedule.schema.ts index eff9853..f0987d1 100644 --- a/src/data/form/schedule/updateSchedule.schema.ts +++ b/src/data/form/schedule/updateSchedule.schema.ts @@ -5,20 +5,27 @@ export const UpdateScheduleSchema = z.object({ .string() , name: z .string() + .nonempty() , startDate: z .date() , endDate: z .date() , status: z .string() - .default("yet") + , content: z + .string() , type: z .string() - .default("once") , style: z .string() , startTime: z .string() , endTime: z .string() + , dayList: z + .string() + .optional() + , participantList: z + .array(z.string()) + .optional() }); \ No newline at end of file diff --git a/src/index.css b/src/index.css index f8cbd26..adf57a0 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +1,4 @@ +@import url("https://cdn.jsdelivr.net/gh/wanteddev/wanted-sans@v1.0.3/packages/wanted-sans/fonts/webfonts/variable/split/WantedSansVariable.min.css"); @import "tailwindcss"; @import "tw-animate-css"; @@ -115,10 +116,43 @@ @apply border-border outline-ring/50; } body { + font-family: "Wanted Sans Variable", "Wanted Sans", -apple-system,BlinkMacSystemFont, system-ui, "Segeo UI", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", "Apple Color Emoji", "Segeo UI Emoji", "Segeo UI Symbol", sans-serif; @apply bg-background text-foreground; } } +@layer utilities { + /* Tailwind의 굵기 유틸리티를 @apply로 재정의 */ + /* 일반적인 가변 폰트의 wght 축 값을 사용 (400~700 사이) */ + .font-thin { + font-variation-settings: 'wght' 100; + } + .font-extralight { + font-variation-settings: 'wght' 200; + } + .font-light { + font-variation-settings: 'wght' 300; + } + .font-normal, .font-regular { /* font-normal 및 font-regular 모두 400 */ + font-variation-settings: 'wght' 400; + } + .font-medium { + font-variation-settings: 'wght' 500; + } + .font-semibold { + font-variation-settings: 'wght' 600; + } + .font-bold { + font-variation-settings: 'wght' 700; + } + .font-extrabold { + font-variation-settings: 'wght' 800; + } + .font-black { + font-variation-settings: 'wght' 900; + } +} + html, body, #root { width: 100%; height: 100%; diff --git a/src/network/BaseNetwork.ts b/src/network/BaseNetwork.ts index c5e9cd3..120c8ca 100644 --- a/src/network/BaseNetwork.ts +++ b/src/network/BaseNetwork.ts @@ -175,7 +175,18 @@ export class BaseNetwork { if (rawData) { const instance = plainToInstance(dtoClass, rawData); - await validateOrReject(instance); + + try { + if (Array.isArray(instance)) { + for (const item of instance) { + await validateOrReject(item); + } + } else { + await validateOrReject(instance); + } + } catch (e) { + console.log(e); + } (result.data as any).data = instance; } } diff --git a/src/network/ScheduleNetwork.ts b/src/network/ScheduleNetwork.ts index ef15dd3..4fa711b 100644 --- a/src/network/ScheduleNetwork.ts +++ b/src/network/ScheduleNetwork.ts @@ -1,36 +1,33 @@ import { BaseNetwork } from "./BaseNetwork" import { - ScheduleListRequest, - CreateScheduleRequest, UpdateScheduleRequest, DeleteScheduleRequest } from '@/data/request'; -import { - CreateScheduleResponse, - ScheduleDetailResponse, - ScheduleListResponse -} from "@/data/response"; import { HttpApiUrl } from "@baekyangdan/core-utils"; -import { SchedulerDTO as DTO } from "@baekyangdan/core-utils"; +import { SchedulerDTO } from "@baekyangdan/core-utils"; const ScheduleApi = HttpApiUrl.Schedule; export class ScheduleNetwork extends BaseNetwork { private baseUrl = ScheduleApi.base; - async getList(data: DTO.ScheduleListRequest) { - return await this.post( + async getList(data: SchedulerDTO.ScheduleListRequest) { + return await this.post( this.baseUrl, - data + data, + undefined, + SchedulerDTO.ScheduleList ); } async getDetail(id: string) { - return await this.get( - `${this.baseUrl}/${id}` + return await this.get( + `${this.baseUrl}/${id}`, + undefined, + SchedulerDTO.ScheduleDetail ); } - async create(data: DTO.ScheduleCreateRequest) { - return await this.post( + async create(data: SchedulerDTO.ScheduleCreateRequest) { + return await this.post( `${this.baseUrl}${ScheduleApi.create}`, data ); diff --git a/src/ui/component/calendar/CustomCalendar.tsx b/src/ui/component/calendar/CustomCalendar.tsx index f8fb610..d27bc93 100644 --- a/src/ui/component/calendar/CustomCalendar.tsx +++ b/src/ui/component/calendar/CustomCalendar.tsx @@ -10,9 +10,6 @@ import { CustomCalendarCN } from "./CustomCalendarCN"; import { toast } from "sonner"; import type { SchedulePopoverMode } from "@/const/schedule/SchedulePopoverMode"; import { SchedulerDTO as DTO, Type } from '@baekyangdan/core-utils'; -interface CustomCalendarProps { - data?: any; -} interface EventBarPosition extends DTO.ScheduleList { positionStyle: React.CSSProperties; @@ -26,12 +23,13 @@ const TOP_OFFSET_FROM_CELL = 35; const DATE_FORMAT_ARIA = 'EEEE, MMMM do, yyyy'; const DATE_FORMAT_KEY = 'yyyyMMdd'; -export const CustomCalendar = ({ data }: CustomCalendarProps) => { +export const CustomCalendar = () => { const [isLoading, setIsLoading] = useState(false); const [refetchTrigger, setRefetchTrigger] = useState(0); const [weekCount, setWeekCount] = useState(5); const [selectedDate, setSelectedDate] = useState(undefined); const [popoverOpen, setPopoverOpen] = useState(false); + const [popoverOpenCoolDown, setPopoverOpenCoolDown] = useState(false); const [popoverSide, setPopoverSide] = useState<'right' | 'left'>('right'); const [popoverAlign, setPopoverAlign] = useState<'start' | 'end'>('end'); const [popoverMode, setPopoverMode] = useState('list'); @@ -97,7 +95,7 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { } as DTO.ScheduleListRequest; const result = await scheduleNetwork.getList(data); - + if (result.success) { if (result.data) { if (isSameMonth(requestedMonth, month)) { @@ -163,7 +161,6 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { if (!isNaN(parsedDate.getTime())) { const dateKey = format(parsedDate, DATE_FORMAT_KEY); - console.log(dateKey); cellInfoMap.set(dateKey, { cell: dayButton, rect: dayButton.getBoundingClientRect() @@ -251,8 +248,8 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { const renderStartDate = startDateObj > calendarStart ? startDateObj : calendarStart; const renderEndDate = endDateObj < calendarEnd ? endDateObj : calendarEnd; - const renderStartKey = format(renderStartDate, DATE_FORMAT_KEY); - const renderEndKey = format(renderEndDate, DATE_FORMAT_KEY); + // const renderStartKey = format(renderStartDate, DATE_FORMAT_KEY); + // const renderEndKey = format(renderEndDate, DATE_FORMAT_KEY); const allRenderDays = eachDayOfInterval({ start: renderStartDate, end: renderEndDate }); @@ -366,52 +363,56 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { const handleOpenChange = (open: boolean) => { setPopoverOpen(open); + setPopoverDetailId(''); if (!open) { setTimeout(() => { setSelectedDate(undefined); + setPopoverMode('list'); }, 150); } } const handleDaySelect = (date: Date | undefined) => { + if (popoverOpenCoolDown) return; if (!date) { + setPopoverOpenCoolDown(true); setPopoverOpen(false); + setPopoverDetailId(''); setTimeout(() => { + setPopoverOpenCoolDown(false); setSelectedDate(undefined); - setPopoverDetailId(''); setPopoverMode('list'); }, 150); return; } - if (date) { - setSelectedDate(date); - const dayOfWeek = date.getDay(); + setSelectedDate(date); - if (0 <= dayOfWeek && dayOfWeek < 4) { - setPopoverSide('right'); - } else { - setPopoverSide('left'); - } + const dayOfWeek = date.getDay(); - const options = { weekStartsOn: 0 as 0 }; - - const totalWeeks = getWeeksInMonth(date, options); - - const currentWeekNumber = getWeekOfMonth(date, options); - - const threshold = Math.ceil(totalWeeks / 2); - - if (currentWeekNumber <= threshold) { - setPopoverAlign('start'); - } else { - setPopoverAlign('end'); - } - - requestAnimationFrame(() => { - setPopoverOpen(true); - }) + if (0 <= dayOfWeek && dayOfWeek < 4) { + setPopoverSide('right'); + } else { + setPopoverSide('left'); } + + const options = { weekStartsOn: 0 as 0 }; + + const totalWeeks = getWeeksInMonth(date, options); + + const currentWeekNumber = getWeekOfMonth(date, options); + + const threshold = Math.ceil(totalWeeks / 2); + + if (currentWeekNumber <= threshold) { + setPopoverAlign('start'); + } else { + setPopoverAlign('end'); + } + + requestAnimationFrame(() => { + setPopoverOpen(true); + }); } const findDateFromClick = ( @@ -455,7 +456,6 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { handleDaySelect(clickedDate); } else { handleDaySelect(undefined); - } } @@ -513,7 +513,7 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { id={pos.segmentId} className={cn( `flex flex-row justify-start items-center absolute select-none`, - "py-0.5 px-2 rounded-sm text-xs text-white overflow-hidden" + "py-0.5 px-2 rounded-sm text-xs font-thin text-white overflow-hidden" )} style={{...pos.positionStyle, backgroundColor: pos.style}} > @@ -530,7 +530,7 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { setDetailId={setPopoverDetailId} popoverSide={popoverSide} popoverAlign={popoverAlign} - onScheduleCreated={refetchList} + reqRefetchList={refetchList} /> diff --git a/src/ui/component/schedule/SchedulePopover.tsx b/src/ui/component/schedule/SchedulePopover.tsx index 4e89bf4..f3f1202 100644 --- a/src/ui/component/schedule/SchedulePopover.tsx +++ b/src/ui/component/schedule/SchedulePopover.tsx @@ -1,9 +1,10 @@ import { PopoverContent } from '@/components/ui/popover'; -import { useEffect, useState } from 'react'; import { ScheduleCreateContent } from './content/ScheduleCreateContent'; import { ScheduleListContent } from './content/ScheduleListContent'; import { ScheduleDetailContent } from './content/ScheduleDetailContent'; import type { SchedulePopoverMode } from '@/const/schedule/SchedulePopoverMode'; +import { ScheduleUpdateContent } from './content/ScheduleUpdateContent'; +import { memo } from 'react'; interface ScheduleSheetProps { date: Date | undefined; @@ -14,76 +15,61 @@ interface ScheduleSheetProps { setMode: (mode: SchedulePopoverMode) => void; detailId: string; setDetailId: (id: string) => void; - onScheduleCreated: () => void; + reqRefetchList: () => void; } -export const SchedulePopover = ({ date, open, mode, setMode, detailId, setDetailId, popoverSide, popoverAlign, onScheduleCreated }: ScheduleSheetProps) => { - - - useEffect(() => { - if (!open) { - setTimeout(() => { - setMode('list'); - }, 150); - } - }, [open]); - - const DetailContent = () => { - return ( -
- Detail -
- ) - } - - const UpdateContent = () => { - return ( -
- Update -
- ) - } - - const SchedulePopoverContent = () => { - switch(mode) { - case 'list': - return - case 'create': - return - case 'detail': - return - case 'update': - return - } - } +export const SchedulePopover = memo(({ mode, ...props}: ScheduleSheetProps) => { return ( - {} + {} ) -} \ No newline at end of file +}); + +const SchedulePopoverContent = memo(({ mode, setMode, setDetailId, date, popoverAlign, popoverSide, open, reqRefetchList, detailId }: ScheduleSheetProps) => { + switch(mode) { + case 'list': + return + case 'create': + return + case 'detail': + return + case 'update': + return + } +}); + +SchedulePopover.displayName = "SchedulePopover"; \ No newline at end of file diff --git a/src/ui/component/schedule/content/ContentProps.ts b/src/ui/component/schedule/content/ContentProps.ts index 28201a9..682978c 100644 --- a/src/ui/component/schedule/content/ContentProps.ts +++ b/src/ui/component/schedule/content/ContentProps.ts @@ -18,4 +18,9 @@ export interface ScheduleListContentProps extends BaseProps { export interface ScheduleDetailContentProps extends BaseProps { id: string; +} + +export interface ScheduleUpdateContentProps extends BaseProps { + refetchList: () => void; + id: string; } \ No newline at end of file diff --git a/src/ui/component/schedule/content/ScheduleCreateContent.tsx b/src/ui/component/schedule/content/ScheduleCreateContent.tsx index d13c537..02976d4 100644 --- a/src/ui/component/schedule/content/ScheduleCreateContent.tsx +++ b/src/ui/component/schedule/content/ScheduleCreateContent.tsx @@ -24,7 +24,6 @@ import { DatePickPopover } from '../popover/DatePickPopover'; import { TimePickPopover } from '../popover/TimePickPopover'; import { TypePickPopover } from '../popover/TypePickPopover'; import type { ScheduleCreateContentProps } from './ContentProps'; -import { Converter } from '@/util/Converter'; import { SchedulerDTO as DTO } from '@baekyangdan/core-utils'; export const ScheduleCreateContent = ({ date, setMode, popoverSide, popoverAlign, refetchList }: ScheduleCreateContentProps) => { @@ -52,8 +51,7 @@ export const ScheduleCreateContent = ({ date, setMode, popoverSide, popoverAlign type: "once", status: "yet", style: getPaletteByKey('SerenityBlue').style, - dayList: "", - participantList: [] + dayList: "" } }); @@ -73,18 +71,6 @@ export const ScheduleCreateContent = ({ date, setMode, popoverSide, popoverAlign const reqCreate = async () => { if (isLoading) return; - // const data = { - // name, - // startDate: Converter.dateToUTC9(startDate), - // endDate: Converter.dateToUTC9(endDate), - // content, - // startTime: standardTimeToContinentalTime(startTime), - // endTime: standardTimeToContinentalTime(endTime), - // type: type as Type.Type, - // status: status as Type.Status, - // style - // } as DTO.ScheduleCreateRequest; - const data = { name, startDate: startDate, @@ -212,111 +198,117 @@ export const ScheduleCreateContent = ({ date, setMode, popoverSide, popoverAlign } return ( -
-
-
setMode('list')} - > - -
- ( - - - - - )} - /> - -
- -
- -
- - -
- -
- +
+
+
setMode('list')} > - {renderContent()} +
-
- selectTime('startTime', time ?? '')} - endTime={endTime} - setEndTime={(time: string | undefined) => selectTime('endTime', time ?? '')} - /> -
- ( -