- 일정 색상 로직 수정: 커스텀 컬러 삭제 - 일정 시작/종료일 설정 구현 - 일정 타입 구현 - 일정 시작/종료 시간 구현 중
This commit is contained in:
21
src/App.tsx
21
src/App.tsx
@@ -7,30 +7,16 @@ import { PageRouting } from './const/PageRouting';
|
||||
import LoginPage from './ui/page/account/login/LoginPage';
|
||||
import ResetPasswordPage from './ui/page/account/resetPassword/ResetPasswordPage';
|
||||
import { HomePage } from './ui/page/home/HomePage';
|
||||
import type { AuthData } from './data/AuthData';
|
||||
import { useEffect } from 'react';
|
||||
import { ScheduleMainPage } from './ui/page/schedule/ScheduleMainPage';
|
||||
import { BaseNetwork } from './network/BaseNetwork';
|
||||
import { TempPage } from './ui/page/home/TempPage';
|
||||
|
||||
function App() {
|
||||
const { authData, login } = useAuthStore();
|
||||
const baseNetwork = new BaseNetwork();
|
||||
useEffect(() => {
|
||||
const autoLogin = localStorage.getItem('autoLogin') === 'true';
|
||||
if (autoLogin) {
|
||||
try {
|
||||
(async () => {
|
||||
await baseNetwork.refreshToken();
|
||||
})();
|
||||
} catch (err) {
|
||||
localStorage.setItem('autoLogin', 'false');
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
const { authData } = useAuthStore();
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route element={<TempPage />} path={"/"} /> {/* 자동로그인용 대기 화면 */}
|
||||
<Route element={<Layout />}>
|
||||
{
|
||||
!authData
|
||||
@@ -39,7 +25,6 @@ function App() {
|
||||
<Route element={<SignUpPage />} path={PageRouting["SIGN_UP"].path} />
|
||||
<Route element={<ResetPasswordPage />} path={PageRouting["RESET_PASSWORD"].path} />
|
||||
<Route element={<Navigate to={PageRouting["LOGIN"].path} />} path="*" />
|
||||
<Route element={<div />} path={"/"} /> {/* 자동로그인용 대기 화면 */}
|
||||
</>
|
||||
: <>
|
||||
<Route element={<Navigate to={PageRouting["HOME"].path} />} path="*" />
|
||||
|
||||
@@ -58,7 +58,7 @@ function Calendar({
|
||||
return '';
|
||||
},
|
||||
formatMonthDropdown: (date) =>
|
||||
date.toLocaleString("", { month: "short" }),
|
||||
date.toLocaleString("default", { month: "short" }),
|
||||
...formatters,
|
||||
}}
|
||||
classNames={{
|
||||
|
||||
@@ -13,7 +13,7 @@ function ScrollArea({
|
||||
return (
|
||||
<ScrollAreaPrimitive.Root
|
||||
data-slot="scroll-area"
|
||||
className={cn("relative", className)}
|
||||
className={cn("relative", "[&>div>div:last-child]:hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport
|
||||
|
||||
@@ -1,99 +1,88 @@
|
||||
export type ColorPaletteType = {
|
||||
index: number;
|
||||
style: string;
|
||||
main: boolean;
|
||||
}
|
||||
|
||||
|
||||
export const ColorPalette: Record<any, ColorPaletteType> = {
|
||||
Black: {
|
||||
index: 0,
|
||||
style: '#000000',
|
||||
main: true
|
||||
style: '#000000'
|
||||
},
|
||||
White: {
|
||||
index: 1,
|
||||
style: '#FFFFFF',
|
||||
main: true
|
||||
style: '#FFFFFF'
|
||||
},
|
||||
PeachCream: {
|
||||
SerenityBlue: {
|
||||
index: 2,
|
||||
style: '#FFDAB9',
|
||||
main: false
|
||||
style: '#92A8D1'
|
||||
},
|
||||
CoralPink: {
|
||||
index: 3,
|
||||
style: '#F08080',
|
||||
main: true
|
||||
style: '#F08080'
|
||||
},
|
||||
MintIcing: {
|
||||
index: 4,
|
||||
style: '#C1E1C1',
|
||||
main: false
|
||||
style: '#C1E1C1'
|
||||
},
|
||||
Vanilla: {
|
||||
index: 5,
|
||||
style: '#FFFACD',
|
||||
main: true
|
||||
style: '#FFFACD'
|
||||
},
|
||||
Wheat: {
|
||||
index: 6,
|
||||
style: '#F5DEB3',
|
||||
main: false
|
||||
style: '#F5DEB3'
|
||||
},
|
||||
AliceBlue: {
|
||||
index: 7,
|
||||
style: '#F0F8FF',
|
||||
main: true
|
||||
style: '#F0F8FF'
|
||||
},
|
||||
Lavender: {
|
||||
index: 8,
|
||||
style: '#E6E6FA',
|
||||
main: false
|
||||
style: '#E6E6FA'
|
||||
},
|
||||
LightAqua: {
|
||||
SageGreen: {
|
||||
index: 9,
|
||||
style: '#A8E6CF',
|
||||
main: true
|
||||
style: '#b2ac88'
|
||||
},
|
||||
CloudWhite: {
|
||||
index: 10,
|
||||
style: '#F0F8FF',
|
||||
main: false
|
||||
style: '#F2F2ED'
|
||||
},
|
||||
LightGray: {
|
||||
index: 11,
|
||||
style: '#D3D3D3',
|
||||
main: true
|
||||
style: '#D3D3D3'
|
||||
},
|
||||
LightKhakki: {
|
||||
index: 12,
|
||||
style: '#F0F8E6',
|
||||
main: false
|
||||
style: '#F0F8E6'
|
||||
},
|
||||
DustyRose: {
|
||||
index: 13,
|
||||
style: '#D8BFD8',
|
||||
main: true
|
||||
style: '#D8BFD8'
|
||||
},
|
||||
CreamBeige: {
|
||||
index: 14,
|
||||
style: '#FAF0E6',
|
||||
main: true,
|
||||
style: '#FAF0E6'
|
||||
},
|
||||
Oatmeal: {
|
||||
index: 15,
|
||||
style: '#FDF5E6',
|
||||
main: false
|
||||
style: '#FDF5E6'
|
||||
},
|
||||
CharcoalLight: {
|
||||
index: 16,
|
||||
style: '#A9A9A9',
|
||||
main: true
|
||||
style: '#A9A9A9'
|
||||
},
|
||||
Custom: {
|
||||
PeachCream: {
|
||||
index: 17,
|
||||
style: 'transparent',
|
||||
main: false
|
||||
style: '#FFDAB9'
|
||||
},
|
||||
LavenderBlue: {
|
||||
index: 18,
|
||||
style :'#CCCCFF'
|
||||
},
|
||||
SeaFoamGreen: {
|
||||
index: 19,
|
||||
style: '#93E9BE'
|
||||
}
|
||||
}
|
||||
9
src/const/schedule/ScheduleDay.ts
Normal file
9
src/const/schedule/ScheduleDay.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export const ScheduleDay: Record<number, string> = {
|
||||
1: '일',
|
||||
2: '월',
|
||||
3: '화',
|
||||
4: '수',
|
||||
5: '목',
|
||||
6: '금',
|
||||
7: '토'
|
||||
}
|
||||
@@ -1 +1,6 @@
|
||||
export type ScheduleStatus = 'yet' | 'completed';
|
||||
|
||||
export const ScheduleStatusLabel: Record<ScheduleStatus, string> = {
|
||||
'yet': '미완료',
|
||||
'completed': '완료'
|
||||
};
|
||||
@@ -1 +1,9 @@
|
||||
export type ScheduleType = 'once' | 'daily' | 'weekly' | 'aweekly' | 'monthly' | 'annual';
|
||||
export type ScheduleType = 'once' | 'daily' | 'weekly' | 'monthly' | 'annual';
|
||||
|
||||
export const ScheduleTypeLabel: Record<ScheduleType, string> = {
|
||||
'once': '한 번만',
|
||||
'daily': '매일',
|
||||
'weekly': '매주',
|
||||
'monthly': '매월',
|
||||
'annual': '매년'
|
||||
};
|
||||
@@ -19,4 +19,6 @@ export const CreateScheduleSchema = z.object({
|
||||
.string()
|
||||
, endTime: z
|
||||
.string()
|
||||
, dayList: z
|
||||
.string()
|
||||
});
|
||||
@@ -10,12 +10,10 @@ export function usePalette() {
|
||||
const getMainPaletteList = () => {
|
||||
const paletteKeys = Object.keys(ColorPalette);
|
||||
let paletteList: ColorPaletteType[] = [];
|
||||
paletteKeys.forEach((paletteKey) => {
|
||||
paletteKeys.slice(0, 10).forEach((paletteKey) => {
|
||||
const key = paletteKey as keyof typeof ColorPalette;
|
||||
const palette: ColorPaletteType = ColorPalette[key];
|
||||
if (palette.main) {
|
||||
paletteList.push(palette);
|
||||
}
|
||||
});
|
||||
|
||||
paletteList = paletteList.sort((a, b) => a.index - b.index);
|
||||
@@ -26,12 +24,11 @@ export function usePalette() {
|
||||
const getExtraPaletteList = () => {
|
||||
const paletteKeys = Object.keys(ColorPalette);
|
||||
let paletteList: ColorPaletteType[] = [];
|
||||
paletteKeys.forEach((paletteKey) => {
|
||||
paletteKeys.slice(10).forEach((paletteKey) => {
|
||||
const key = paletteKey as keyof typeof ColorPalette;
|
||||
const palette: ColorPaletteType = ColorPalette[key];
|
||||
if (!palette.main) {
|
||||
paletteList.push(palette);
|
||||
}
|
||||
|
||||
});
|
||||
paletteList = paletteList.sort((a, b) => a.index - b.index);
|
||||
return paletteList;
|
||||
@@ -43,12 +40,6 @@ export function usePalette() {
|
||||
return ColorPalette[key];
|
||||
}
|
||||
|
||||
const getCustomColor = (style: string) => {
|
||||
return {
|
||||
style: `#${style}`,
|
||||
main: false
|
||||
} as ColorPaletteType;
|
||||
}
|
||||
|
||||
const getStyle = (palette: ColorPaletteType) => {
|
||||
return palette.style;
|
||||
@@ -61,7 +52,6 @@ export function usePalette() {
|
||||
getExtraPaletteList,
|
||||
getAllPaletteList,
|
||||
getPaletteByKey,
|
||||
getCustomColor,
|
||||
getStyle
|
||||
}
|
||||
}
|
||||
13
src/hooks/use-record.ts
Normal file
13
src/hooks/use-record.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
export function useRecord(record: Record<any, any>) {
|
||||
const keys = useMemo(() => {
|
||||
return Object.keys(record);
|
||||
}, [record]);
|
||||
|
||||
const values = useMemo(() => {
|
||||
return Object.values(record);
|
||||
}, [record]);
|
||||
|
||||
return { keys, values };
|
||||
}
|
||||
@@ -134,19 +134,19 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.rdp-day {
|
||||
aspect-ratio: unset;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.rdp-week:not(:first-child) {
|
||||
@apply border-t;
|
||||
.custom-rdp-day {
|
||||
aspect-ratio: unset!;
|
||||
}
|
||||
|
||||
.rdp-day:not(:first-child) {
|
||||
@apply border-l;
|
||||
.custom-rdp-week:not(:first-child) {
|
||||
@apply border-t!;
|
||||
}
|
||||
|
||||
.custom-rdp-day:not(:first-child) {
|
||||
@apply border-l!;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { getDefaultClassNames } from "react-day-picker";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { isSameDay, getWeeksInMonth, getWeekOfMonth } from "date-fns";
|
||||
import { SchedulePopover } from "../popover/SchedulePopover";
|
||||
import { SchedulePopover } from "../popover/schedule/SchedulePopover";
|
||||
|
||||
interface CustomCalendarProps {
|
||||
data?: any;
|
||||
@@ -127,11 +127,13 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => {
|
||||
),
|
||||
week: cn(
|
||||
defaultClassNames.week,
|
||||
`w-full`
|
||||
`w-full`,
|
||||
'custom-rdp-week'
|
||||
),
|
||||
day: cn(
|
||||
defaultClassNames.day,
|
||||
`w-[calc(100%/7)] rounded-none`
|
||||
`w-[calc(100%/7)] rounded-none`,
|
||||
'custom-rdp-day'
|
||||
),
|
||||
day_button: cn(
|
||||
defaultClassNames.day_button,
|
||||
@@ -181,6 +183,7 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => {
|
||||
/>
|
||||
<SchedulePopover
|
||||
date={selectedDate}
|
||||
open={popoverOpen}
|
||||
popoverSide={popoverSide}
|
||||
popoverAlign={popoverAlign}
|
||||
/>
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Sheet, SheetContent, SheetHeader } from '@/components/ui/sheet';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { usePalette } from '@/hooks/use-palette';
|
||||
import { type ColorPaletteType } from '@/const/ColorPalette';
|
||||
import { ColorPickPopover } from './ColorPickPopover';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import * as z from 'zod';
|
||||
import { CreateScheduleSchema } from '@/data/form/createSchedule.schema';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Field, FieldError } from '@/components/ui/field';
|
||||
import { ArrowLeft, PenSquare, X } from 'lucide-react';
|
||||
|
||||
interface ScheduleSheetProps {
|
||||
date: Date | undefined;
|
||||
popoverSide: 'left' | 'right';
|
||||
popoverAlign: 'start' | 'end';
|
||||
}
|
||||
|
||||
export const SchedulePopover = ({ date, popoverSide, popoverAlign }: ScheduleSheetProps) => {
|
||||
const {
|
||||
getPaletteByKey,
|
||||
} = usePalette();
|
||||
const defaultColor = getPaletteByKey('Black');
|
||||
const [scheduleColor, setScheduleColor] = useState(defaultColor);
|
||||
const [colorPopoverOpen, setColorPopoverOpen] = useState(false);
|
||||
const [mode, setMode] = useState<'list' | 'detail' | 'create' | 'edit'>('list');
|
||||
const selectColor = (color: ColorPaletteType) => {
|
||||
setScheduleColor(color);
|
||||
setColorPopoverOpen(false);
|
||||
}
|
||||
|
||||
const createScheduleForm = useForm<z.infer<typeof CreateScheduleSchema>>({
|
||||
resolver: zodResolver(CreateScheduleSchema),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
startDate: date,
|
||||
endDate: date,
|
||||
content: "",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
type: "once",
|
||||
status: "yet",
|
||||
style: defaultColor.style,
|
||||
}
|
||||
});
|
||||
|
||||
const Content = () => {
|
||||
switch(mode) {
|
||||
case 'list':
|
||||
return (
|
||||
<div>
|
||||
<PenSquare onClick={() => setMode('create')}/>
|
||||
</div>
|
||||
);
|
||||
case 'create':
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col justify-start items-start gap-2.5">
|
||||
<div className="w-full flex flex-row justify-center items-center gap-5">
|
||||
<Popover open={colorPopoverOpen} onOpenChange={setColorPopoverOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-full w-5 h-5 border-2 border-gray-300',
|
||||
)}
|
||||
style={{
|
||||
backgroundColor: `${scheduleColor.style !== 'transparent' && scheduleColor.style}`,
|
||||
background: `${scheduleColor.style === 'transparent' && 'linear-gradient(135deg, black 50%, white 50%)' }`
|
||||
}}
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<ColorPickPopover
|
||||
setColor={selectColor}
|
||||
/>
|
||||
</Popover>
|
||||
<Controller
|
||||
name="name"
|
||||
control={createScheduleForm.control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Field data-invalid={fieldState.invalid}>
|
||||
<Input
|
||||
{...field}
|
||||
id="form-create-schedule-name"
|
||||
placeholder="제목"
|
||||
className="font-bold border-t-0 border-r-0 border-l-0 p-0 border-b-2 rounded-none shadow-none border-indigo-300 focus-visible:ring-0 focus-visible:border-b-indigo-500"
|
||||
style={{
|
||||
fontSize: '20px'
|
||||
}}
|
||||
tabIndex={1}
|
||||
arai-invalid={fieldState.invalid}
|
||||
/>
|
||||
<FieldError errors={[fieldState.error]} />
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="flex flex-row self-end justify-self-end items-center justify-center cursor-default"
|
||||
onClick={() => setMode('list')}
|
||||
>
|
||||
<X />
|
||||
<span>닫기</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
default: return (<></>)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<PopoverContent
|
||||
className="rounded-xl xl:w-[calc(100vw/4)] xl:max-w-[480px] min-w-[320px]"
|
||||
align={popoverAlign} side={popoverSide}
|
||||
>
|
||||
<ScrollArea
|
||||
className={
|
||||
cn(
|
||||
"[&>div>div:last-child]:hidden min-h-[125px] h-[calc(100vh/2)] p-2.5 w-full flex flex-col",
|
||||
)
|
||||
}
|
||||
>
|
||||
{<Content />}
|
||||
</ScrollArea>
|
||||
</PopoverContent>
|
||||
)
|
||||
}
|
||||
@@ -51,8 +51,7 @@ export const ColorPickPopover = ({ setColor }: ColorPickPopoverProps) => {
|
||||
<div
|
||||
className="rounded-full w-5 h-5 border border-gray-300"
|
||||
style={{
|
||||
backgroundColor: `${palette.style !== 'transparent' && palette.style}`,
|
||||
background: `${palette.style === 'transparent' && 'linear-gradient(135deg, black 50%, white 50%)' }`
|
||||
backgroundColor: `${palette.style}`
|
||||
}}
|
||||
onClick={() => setColor(palette)}
|
||||
/>
|
||||
129
src/ui/component/popover/schedule/DatePickPopover.tsx
Normal file
129
src/ui/component/popover/schedule/DatePickPopover.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { format } from "date-fns";
|
||||
import { useState } from "react";
|
||||
|
||||
interface BaseProps {
|
||||
disabled: boolean;
|
||||
popoverAlign: 'start' | 'end';
|
||||
}
|
||||
|
||||
interface SingleModeProps extends BaseProps {
|
||||
mode: 'single';
|
||||
date: Date | undefined;
|
||||
setDate: (date: Date | undefined) => void;
|
||||
}
|
||||
|
||||
interface RangeModeProps extends BaseProps {
|
||||
mode: 'range';
|
||||
startDate: Date | undefined;
|
||||
endDate: Date | undefined;
|
||||
setStartDate: (date: Date | undefined) => void;
|
||||
setEndDate: (date: Date | undefined) => void;
|
||||
}
|
||||
|
||||
type DaetPickPopoverProps = SingleModeProps | RangeModeProps;
|
||||
|
||||
export const DatePickPopover = ({ ...props } : DaetPickPopoverProps) => {
|
||||
const { mode, popoverAlign, disabled } = props;
|
||||
|
||||
if (mode === 'single') {
|
||||
const { date, setDate } = props;
|
||||
const [open, setOpen] = useState(false);
|
||||
const onDaySelected = (open: boolean) => {
|
||||
setOpen(open);
|
||||
}
|
||||
return(
|
||||
<div>{date?.toString()}</div>
|
||||
)
|
||||
}
|
||||
|
||||
const { startDate, setStartDate, endDate, setEndDate } = props;
|
||||
const [startOpen, setStartOpen] = useState(false);
|
||||
const [endOpen, setEndOpen] = useState(false);
|
||||
|
||||
const onStartDaySelected = (date: Date | undefined) => {
|
||||
setStartDate(date);
|
||||
setStartOpen(false);
|
||||
}
|
||||
|
||||
const onEndDaySelected = (date: Date | undefined) => {
|
||||
setEndDate(date);
|
||||
setEndOpen(false);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-full h-full flex flex-row justify-around items-center"
|
||||
>
|
||||
<Popover
|
||||
open={startOpen}
|
||||
onOpenChange={setStartOpen}
|
||||
>
|
||||
<PopoverTrigger>
|
||||
<Button
|
||||
className="border border-indigo-100 bg-white hover:bg-indigo-100 text-black"
|
||||
disabled={disabled}
|
||||
>
|
||||
{
|
||||
!startDate
|
||||
? "시작일 선택"
|
||||
: format(startDate, "yyyy년 MM월 dd일")
|
||||
}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-fit h-fit"
|
||||
align={'start'}
|
||||
side={popoverAlign === 'start' ? 'bottom' : 'top'}
|
||||
>
|
||||
<Calendar
|
||||
mode={'single'}
|
||||
onSelect={onStartDaySelected}
|
||||
disabled={
|
||||
endDate
|
||||
? { after: endDate }
|
||||
: undefined
|
||||
}
|
||||
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<span> - </span>
|
||||
<Popover
|
||||
open={endOpen}
|
||||
onOpenChange={setEndOpen}
|
||||
>
|
||||
<PopoverTrigger>
|
||||
<Button
|
||||
className="border border-indigo-100 bg-white hover:bg-indigo-100 text-black"
|
||||
disabled={disabled}
|
||||
>
|
||||
{
|
||||
!endDate
|
||||
? "종료일 선택"
|
||||
: format(endDate, "yyyy년 MM월 dd일")
|
||||
}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-fit h-fit"
|
||||
align={'end'}
|
||||
side={popoverAlign === 'start' ? 'bottom' : 'top'}
|
||||
>
|
||||
<Calendar
|
||||
mode='single'
|
||||
onSelect={onEndDaySelected}
|
||||
disabled={
|
||||
startDate
|
||||
? { before: startDate }
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
303
src/ui/component/popover/schedule/SchedulePopover.tsx
Normal file
303
src/ui/component/popover/schedule/SchedulePopover.tsx
Normal file
@@ -0,0 +1,303 @@
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { usePalette } from '@/hooks/use-palette';
|
||||
import { ColorPalette, type ColorPaletteType } from '@/const/ColorPalette';
|
||||
import { ColorPickPopover } from './ColorPickPopover';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import * as z from 'zod';
|
||||
import { CreateScheduleSchema } from '@/data/form/createSchedule.schema';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Field, FieldError } from '@/components/ui/field';
|
||||
import { PenSquare, X } from 'lucide-react';
|
||||
import { ScheduleTypeLabel, type ScheduleType } from '@/const/schedule/ScheduleType';
|
||||
import { useRecord } from '@/hooks/use-record';
|
||||
import { TypePickPopover } from './TypePickPopover';
|
||||
import { ScheduleDay } from '@/const/schedule/ScheduleDay';
|
||||
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
||||
import { DatePickPopover } from './DatePickPopover';
|
||||
import { format } from 'date-fns';
|
||||
import { TimePickPopover } from './TimePickPopover';
|
||||
|
||||
interface ScheduleSheetProps {
|
||||
date: Date | undefined;
|
||||
open: boolean;
|
||||
popoverSide: 'left' | 'right';
|
||||
popoverAlign: 'start' | 'end';
|
||||
}
|
||||
|
||||
export const SchedulePopover = ({ date, open, popoverSide, popoverAlign }: ScheduleSheetProps) => {
|
||||
const {
|
||||
getPaletteByKey
|
||||
} = usePalette();
|
||||
const [mode, setMode] = useState<'list' | 'create' | 'detail' | 'update'>('list');
|
||||
const [colorPopoverOpen, setColorPopoverOpen] = useState(false);
|
||||
const dayLabelList = useRecord(ScheduleDay).keys.map((key) => {
|
||||
return {
|
||||
day: Number(key),
|
||||
label: ScheduleDay[Number(key)]
|
||||
} as { day: number, label: string };
|
||||
})
|
||||
const createScheduleForm = useForm<z.infer<typeof CreateScheduleSchema>>({
|
||||
resolver: zodResolver(CreateScheduleSchema),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
startDate: date || new Date(),
|
||||
endDate: date || new Date(),
|
||||
content: "",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
type: "once",
|
||||
status: "yet",
|
||||
style: getPaletteByKey('Black').style,
|
||||
dayList: ""
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
console.log(date);
|
||||
if (open && date) {
|
||||
createScheduleForm.setValue('startDate', date);
|
||||
createScheduleForm.setValue('endDate', date);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
setMode('list');
|
||||
createScheduleForm.clearErrors();
|
||||
createScheduleForm.reset();
|
||||
}, 150);
|
||||
}, [open]);
|
||||
|
||||
const {
|
||||
name,
|
||||
startDate,
|
||||
endDate,
|
||||
content,
|
||||
startTime,
|
||||
endTime,
|
||||
type,
|
||||
status,
|
||||
style,
|
||||
dayList
|
||||
} = createScheduleForm.watch();
|
||||
|
||||
const selectColor = (color: ColorPaletteType) => {
|
||||
createScheduleForm.setValue('style', color.style);
|
||||
setColorPopoverOpen(false);
|
||||
}
|
||||
|
||||
const selectType = (type: ScheduleType) => {
|
||||
createScheduleForm.setValue('type', type);
|
||||
}
|
||||
|
||||
const selectDayList = (newValues: string[]) => {
|
||||
const sortedValues = newValues.sort();
|
||||
const newDayList = sortedValues.join('');
|
||||
createScheduleForm.setValue('dayList', newDayList);
|
||||
}
|
||||
|
||||
const selectDate = (type: 'startDate' | 'endDate', date: Date | undefined) => {
|
||||
if (!date) return;
|
||||
createScheduleForm.setValue(type, date);
|
||||
}
|
||||
|
||||
const selectTime = (type: 'startTime' | 'endTime', time: string) => {
|
||||
createScheduleForm.setValue(type, time);
|
||||
}
|
||||
|
||||
const ListContent = () => {
|
||||
return (
|
||||
<div>
|
||||
<PenSquare onClick={() => setMode('create')}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const CreateContent = () => {
|
||||
|
||||
const OnceContent = () => {
|
||||
return (
|
||||
<div
|
||||
className="w-full h-10"
|
||||
>
|
||||
<DatePickPopover
|
||||
disabled={false}
|
||||
mode={'range'}
|
||||
popoverAlign={popoverAlign}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
setStartDate={(date: Date | undefined) => selectDate('startDate', date)}
|
||||
setEndDate={(date: Date | undefined) => selectDate('endDate', date)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const DailyContent = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const DefaultContent = () => {
|
||||
return (
|
||||
(
|
||||
<ToggleGroup
|
||||
type="multiple"
|
||||
className="w-full h-10 flex flex-row justify-center items-center"
|
||||
onValueChange={selectDayList}
|
||||
>
|
||||
{
|
||||
dayLabelList.map((day) => (
|
||||
<ToggleGroupItem
|
||||
value={`${day.day}`}
|
||||
className="border rounded-none not-last:border-r-0"
|
||||
>
|
||||
{day.label}
|
||||
</ToggleGroupItem>
|
||||
))
|
||||
}
|
||||
</ToggleGroup>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const renderContent = () => {
|
||||
switch (type) {
|
||||
case 'Once':
|
||||
return <OnceContent />;
|
||||
case 'Daily':
|
||||
return <DailyContent />;
|
||||
default:
|
||||
return <DefaultContent />;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col justify-start items-start gap-4">
|
||||
<div className="w-full flex flex-row justify-center items-center gap-5">
|
||||
<Popover open={colorPopoverOpen} onOpenChange={setColorPopoverOpen}>
|
||||
<div className="w-6 h-6 flex justify-center items-center">
|
||||
<PopoverTrigger asChild>
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-full w-5 h-5 border-2 border-gray-300',
|
||||
)}
|
||||
style={{
|
||||
backgroundColor: `${style}`,
|
||||
}}
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
</div>
|
||||
<ColorPickPopover
|
||||
setColor={selectColor}
|
||||
/>
|
||||
</Popover>
|
||||
<Controller
|
||||
name="name"
|
||||
control={createScheduleForm.control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Field data-invalid={fieldState.invalid}>
|
||||
<Input
|
||||
{...field}
|
||||
id="form-create-schedule-name"
|
||||
placeholder="제목"
|
||||
className="font-bold border-t-0 border-r-0 border-l-0 p-0 border-b-2 rounded-none shadow-none border-indigo-300 focus-visible:ring-0 focus-visible:border-b-indigo-500"
|
||||
style={{
|
||||
fontSize: '20px'
|
||||
}}
|
||||
tabIndex={1}
|
||||
arai-invalid={fieldState.invalid}
|
||||
/>
|
||||
<FieldError errors={[fieldState.error]} />
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<div className="hover:bg-gray-100 cursor-default w-full h-10 border flex justify-center items-center rounded-sm">
|
||||
{ScheduleTypeLabel[type as keyof typeof ScheduleTypeLabel]}
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<TypePickPopover
|
||||
setType={selectType}
|
||||
popoverSide={popoverSide}
|
||||
/>
|
||||
</Popover>
|
||||
<div
|
||||
className="w-full h-10"
|
||||
>
|
||||
{renderContent()}
|
||||
</div>
|
||||
<div className="w-full h-10">
|
||||
<TimePickPopover
|
||||
mode='range'
|
||||
popoverAlign={popoverAlign}
|
||||
disabled={false}
|
||||
startTime=''
|
||||
setStartTime={(time: string | undefined) => {}}
|
||||
endTime=''
|
||||
setEndTime={(time: string | undefined) => {}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="flex flex-row self-end justify-self-end items-center justify-center cursor-default"
|
||||
onClick={() => setMode('list')}
|
||||
>
|
||||
<X />
|
||||
<span>취소</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const DetailContent = () => {
|
||||
return (
|
||||
<div>
|
||||
Detail
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const UpdateContent = () => {
|
||||
return (
|
||||
<div>
|
||||
Update
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const SchedulePopoverContent = () => {
|
||||
switch(mode) {
|
||||
case 'list':
|
||||
return <ListContent />
|
||||
case 'create':
|
||||
return <CreateContent />
|
||||
case 'detail':
|
||||
return <DetailContent />
|
||||
case 'update':
|
||||
return <UpdateContent />
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<PopoverContent
|
||||
className="rounded-xl xl:w-[calc(100vw/4)] xl:max-w-[480px] min-w-[384px]"
|
||||
align={popoverAlign} side={popoverSide}
|
||||
>
|
||||
<div>{date && format(date, "yyyy년 MM월 dd일")}</div>
|
||||
<ScrollArea
|
||||
className={
|
||||
cn(
|
||||
"min-h-[125px] h-[calc(100vh/2.5)] p-2.5 w-full flex flex-col",
|
||||
)
|
||||
}
|
||||
>
|
||||
{<SchedulePopoverContent />}
|
||||
</ScrollArea>
|
||||
</PopoverContent>
|
||||
)
|
||||
}
|
||||
135
src/ui/component/popover/schedule/TimePickPopover.tsx
Normal file
135
src/ui/component/popover/schedule/TimePickPopover.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||
|
||||
interface BaseProps {
|
||||
disabled: boolean;
|
||||
popoverAlign: 'start' | 'end';
|
||||
}
|
||||
|
||||
interface SingleModeProps extends BaseProps {
|
||||
mode: 'single';
|
||||
time: string | undefined;
|
||||
setTime: (time: string | undefined) => void;
|
||||
}
|
||||
|
||||
interface RangeModeProps extends BaseProps {
|
||||
mode: 'range';
|
||||
startTime: string | undefined;
|
||||
setStartTime: (time: string | undefined) => void;
|
||||
endTime: string | undefined;
|
||||
setEndTime: (time: string | undefined) => void;
|
||||
}
|
||||
|
||||
type TimePickPopoverProps = SingleModeProps | RangeModeProps;
|
||||
|
||||
export const TimePickPopover = ({ ...props }: TimePickPopoverProps) => {
|
||||
const { mode, disabled, popoverAlign } = props;
|
||||
|
||||
if (mode === 'single') {
|
||||
return (
|
||||
<div>single</div>
|
||||
)
|
||||
}
|
||||
|
||||
// const { startTime, setStartTime, endTime, setEndTime } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-full h-full flex flex-row justify-around items-center"
|
||||
>
|
||||
<Popover
|
||||
>
|
||||
<PopoverTrigger>
|
||||
<Button
|
||||
className="border border-indigo-100 bg-white hover:bg-indigo-100 text-black"
|
||||
disabled={disabled}
|
||||
>
|
||||
시작 시간 설정
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-fit h-42 flex flex-row p-0"
|
||||
align={'start'}
|
||||
side={popoverAlign === 'start' ? 'bottom' : 'top'}
|
||||
>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
className="w-15 flex flex-col justify-start items-center"
|
||||
>
|
||||
<ToggleGroupItem
|
||||
value={"오전"}
|
||||
className="w-full h-7"
|
||||
>
|
||||
오전
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value={"오후"}
|
||||
className="w-full h-7"
|
||||
>
|
||||
오후
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
<ScrollArea
|
||||
className="w-15 h-full"
|
||||
>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
className="w-15 border-r rounded-none border-l flex flex-col justify-start items-center"
|
||||
>
|
||||
{
|
||||
[1,2,3,4,5,6,7,8,9,10,11,12].map((time) => (
|
||||
<ToggleGroupItem
|
||||
className="w-full h-7 rounded-none"
|
||||
value={time.toString().padStart(2, '0')}
|
||||
>
|
||||
{time.toString().padStart(2, '0')}
|
||||
</ToggleGroupItem>
|
||||
))
|
||||
}
|
||||
</ToggleGroup>
|
||||
</ScrollArea>
|
||||
<ScrollArea
|
||||
className="w-15 h-full"
|
||||
>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
className="w-full h-full flex flex-col justify-start items-center"
|
||||
>
|
||||
{
|
||||
Array.from({ length: 60 }).map((_, idx) => (
|
||||
<ToggleGroupItem
|
||||
value={idx.toString().padStart(2, '0')}
|
||||
className="w-full h-7"
|
||||
>
|
||||
{idx.toString().padStart(0, '2')}
|
||||
</ToggleGroupItem>
|
||||
))
|
||||
}
|
||||
</ToggleGroup>
|
||||
</ScrollArea>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<span> - </span>
|
||||
<Popover
|
||||
>
|
||||
<PopoverTrigger>
|
||||
<Button
|
||||
className="border border-indigo-100 bg-white hover:bg-indigo-100 text-black"
|
||||
disabled={disabled}
|
||||
>
|
||||
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-fit h-fit"
|
||||
align={'end'}
|
||||
side={popoverAlign === 'start' ? 'bottom' : 'top'}
|
||||
>
|
||||
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
31
src/ui/component/popover/schedule/TypePickPopover.tsx
Normal file
31
src/ui/component/popover/schedule/TypePickPopover.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { PopoverContent } from "@/components/ui/popover"
|
||||
import { ScheduleTypeLabel, type ScheduleType } from "@/const/schedule/ScheduleType";
|
||||
import { useRecord } from "@/hooks/use-record";
|
||||
|
||||
interface TypePickPopoverProps {
|
||||
popoverSide : 'left' | 'right';
|
||||
setType: (type: ScheduleType) => void;
|
||||
}
|
||||
|
||||
export const TypePickPopover = ({ popoverSide, setType }: TypePickPopoverProps) => {
|
||||
const typeLabelList = useRecord(ScheduleTypeLabel).keys.map((key) => {
|
||||
return {
|
||||
type: key,
|
||||
label: ScheduleTypeLabel[key as keyof typeof ScheduleTypeLabel]
|
||||
} as { type: ScheduleType, label: string};
|
||||
});
|
||||
return (
|
||||
<PopoverContent side={popoverSide} align={'start'} className="p-0 w-fit h-fit">
|
||||
<div className="w-20 h-62.5 flex flex-col">
|
||||
{typeLabelList.map((type) => (
|
||||
<div
|
||||
className="cursor-default flex-1 h-full flex justify-center items-center hover:bg-gray-100"
|
||||
onClick={() => setType(type.type)}
|
||||
>
|
||||
{type.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
)
|
||||
}
|
||||
25
src/ui/page/home/TempPage.tsx
Normal file
25
src/ui/page/home/TempPage.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { PageRouting } from "@/const/PageRouting";
|
||||
import { BaseNetwork } from "@/network/BaseNetwork";
|
||||
import { useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export const TempPage = () => {
|
||||
const baseNetwork = new BaseNetwork();
|
||||
const navigate = useNavigate();
|
||||
useEffect(() => {
|
||||
const autoLogin = localStorage.getItem('autoLogin') === 'true';
|
||||
if (autoLogin) {
|
||||
try {
|
||||
(async () => {
|
||||
await baseNetwork.refreshToken();
|
||||
navigate(PageRouting["HOME"].path);
|
||||
})();
|
||||
} catch (err) {
|
||||
localStorage.setItem('autoLogin', 'false');
|
||||
navigate(PageRouting["LOGIN"].path);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (<div />);
|
||||
}
|
||||
Reference in New Issue
Block a user