feat: 일정 등록 기능
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
import { SchedulePage } from "@/pages/schedule";
|
||||
export default SchedulePage;
|
||||
@@ -0,0 +1,2 @@
|
||||
import { CreatePage } from "@/pages/schedule";
|
||||
export default CreatePage;
|
||||
@@ -14,6 +14,10 @@
|
||||
"@common/config": "workspace:*",
|
||||
"@fontsource-variable/noto-sans-kr": "^5.2.5",
|
||||
"@frontend-opensource/use-react-hooks": "^1.1.4",
|
||||
"@fullcalendar/core": "^6.1.17",
|
||||
"@fullcalendar/daygrid": "^6.1.17",
|
||||
"@fullcalendar/interaction": "^6.1.17",
|
||||
"@fullcalendar/react": "^6.1.17",
|
||||
"@hookform/resolvers": "^4.1.3",
|
||||
"@minoru/react-dnd-treeview": "3.5.1-alpha.1",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
@@ -26,6 +30,7 @@
|
||||
"chart.js": "^4.4.8",
|
||||
"dayjs": "^1.11.13",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lucide-react": "^0.474.0",
|
||||
"next": "15.3.0",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type {
|
||||
Schedule,
|
||||
ScheduleFormData,
|
||||
ScheduleParams,
|
||||
ScheduleService,
|
||||
} from "../model/schedule-interface";
|
||||
|
||||
class ScheduleApiService implements ScheduleService {
|
||||
public async getSchedules(params?: ScheduleParams): Promise<Schedule[]> {
|
||||
const response = await client.get<Schedule[]>(
|
||||
ENDPOINT.CMS_SERVICE.SCHEDULES,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async createSchedule(data: ScheduleFormData): Promise<void> {
|
||||
await client.post(ENDPOINT.CMS_SERVICE.SCHEDULES, data);
|
||||
}
|
||||
}
|
||||
|
||||
export const ScheduleApi = new ScheduleApiService();
|
||||
@@ -0,0 +1,2 @@
|
||||
export { SchedulePage } from "./ui/schedule-page";
|
||||
export { CreatePage } from "./ui/create-page";
|
||||
@@ -0,0 +1,55 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export interface ScheduleParams {
|
||||
siteId?: string;
|
||||
schdlTtl?: string;
|
||||
schdlSeCd?: string;
|
||||
schdlBgngYmd?: string;
|
||||
schdlEndYmd?: string;
|
||||
reptYn?: string;
|
||||
commSchdlYn?: string;
|
||||
useYn?: string;
|
||||
}
|
||||
|
||||
export interface Schedule {
|
||||
schdlSn: number;
|
||||
siteId: string;
|
||||
schdlTtl: string;
|
||||
schdlSeCd: string;
|
||||
schdlSeNm: string;
|
||||
schdlBgngDt: string;
|
||||
schdlEndDt: string;
|
||||
reptYn: string;
|
||||
commSchdlYn: string;
|
||||
}
|
||||
|
||||
const scheduleRepeatSchema = z.object({
|
||||
schdlSn: z.number().nullish(),
|
||||
reptCycleCd: z.string().nullish(),
|
||||
reptDayVl: z.number().nullish(),
|
||||
reptDowVl: z.number().nullish(),
|
||||
reptMmVl: z.number().nullish(),
|
||||
reptQuVl: z.number().nullish(),
|
||||
reptEndDt: z.string().nullish(),
|
||||
});
|
||||
|
||||
export const scheduleFormSchema = z.object({
|
||||
siteId: z.string(),
|
||||
schdlTtl: z.string(),
|
||||
schdlSeCd: z.string(),
|
||||
schdlBgngDt: z.string(),
|
||||
schdlEndDt: z.string(),
|
||||
schdlCn: z.string().nullish(),
|
||||
reptYn: z.string().nullish(),
|
||||
commSchdlYn: z.string().nullish(),
|
||||
useYn: z.string().nullish(),
|
||||
atflCd: z.string().nullish(),
|
||||
reptDto: scheduleRepeatSchema.nullish(),
|
||||
});
|
||||
|
||||
export type ScheduleFormData = z.infer<typeof scheduleFormSchema>;
|
||||
|
||||
export interface ScheduleService {
|
||||
getSchedules(params?: ScheduleParams): Promise<Schedule[]>;
|
||||
createSchedule(data: ScheduleFormData): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
"use client";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { SITE_ID } from "@/shared/config";
|
||||
import {
|
||||
scheduleFormSchema,
|
||||
type ScheduleFormData,
|
||||
} from "../model/schedule-interface";
|
||||
import { ScheduleApi } from "../api/schedule-service";
|
||||
import { ScheduleForm } from "./schedule-form";
|
||||
|
||||
function CreatePage() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const dateStr = searchParams?.get("dateStr");
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
|
||||
const { mutateAsync: createSchedule } = useMutation({
|
||||
mutationFn: ScheduleApi.createSchedule,
|
||||
onSuccess: () => {
|
||||
alert("일정이 등록되었습니다.", router.back);
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm<ScheduleFormData>({
|
||||
resolver: zodResolver(scheduleFormSchema),
|
||||
defaultValues: {
|
||||
siteId: SITE_ID,
|
||||
schdlBgngDt: dateStr
|
||||
? dayjs(dateStr).format("YYYY-MM-DD 00:00:00")
|
||||
: undefined,
|
||||
schdlEndDt: dateStr
|
||||
? dayjs(dateStr).format("YYYY-MM-DD 23:59:59")
|
||||
: undefined,
|
||||
reptYn: "N",
|
||||
commSchdlYn: "N",
|
||||
useYn: "Y",
|
||||
atflCd: null,
|
||||
reptDto: null,
|
||||
},
|
||||
});
|
||||
|
||||
const handleSave = async (data: ScheduleFormData) => {
|
||||
// const reptDtoValid = Object.values(data.reptDto ?? {}).every(
|
||||
// (value) => value === null,
|
||||
// );
|
||||
// if (reptDtoValid) data.reptDto = null;
|
||||
await createSchedule(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card card-border !mb-0 !mt-6">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">스케줄 등록</h3>
|
||||
</div>
|
||||
<ScheduleForm form={form} handleSave={handleSave} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { CreatePage };
|
||||
@@ -0,0 +1,123 @@
|
||||
"use client";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import { useState } from "react";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import {
|
||||
Button,
|
||||
Calendar,
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
ScrollArea,
|
||||
ScrollBar,
|
||||
cn,
|
||||
} from "@common/components";
|
||||
|
||||
type DateTimePickerProps = {
|
||||
date?: Date;
|
||||
classes?: { trigger?: string };
|
||||
onDateChange?: (date: Date) => void;
|
||||
};
|
||||
|
||||
function DateTimePicker({ date, classes, onDateChange }: DateTimePickerProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const hours = Array.from({ length: 24 }, (_, i) => i);
|
||||
const minutes = Array.from({ length: 60 }, (_, i) => i);
|
||||
|
||||
const handleDateSelect = (selectedDate: Date | undefined) => {
|
||||
if (!selectedDate) return;
|
||||
onDateChange?.(selectedDate);
|
||||
};
|
||||
|
||||
const handleTimeChange = (type: "hour" | "minute", value: string) => {
|
||||
if (date) {
|
||||
const newDate = new Date(date);
|
||||
if (type === "hour") {
|
||||
newDate.setHours(parseInt(value));
|
||||
} else if (type === "minute") {
|
||||
newDate.setMinutes(parseInt(value));
|
||||
}
|
||||
onDateChange?.(newDate);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"date-picker-trigger",
|
||||
!date && "text-muted-foreground",
|
||||
classes?.trigger,
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="!size-[1.6rem]" />
|
||||
{date ? (
|
||||
dayjs(date).format("YYYY/MM/DD HH:mm")
|
||||
) : (
|
||||
<span>YYYY/MM/DD HH:mm</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0">
|
||||
<div className="sm:flex">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={handleDateSelect}
|
||||
initialFocus
|
||||
/>
|
||||
<div className="flex flex-col divide-y sm:h-[300px] sm:flex-row sm:divide-x sm:divide-y-0">
|
||||
<ScrollArea className="w-64 sm:w-auto">
|
||||
<div className="flex p-2 sm:flex-col">
|
||||
{hours.reverse().map((hour) => (
|
||||
<Button
|
||||
key={hour}
|
||||
size="icon"
|
||||
className={cn(
|
||||
"aspect-square shrink-0 text-label sm:w-full",
|
||||
date && date.getHours() === hour
|
||||
? "!bg-primary text-primary-foreground"
|
||||
: "!bg-background",
|
||||
)}
|
||||
onClick={() => handleTimeChange("hour", hour.toString())}
|
||||
>
|
||||
{hour}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<ScrollBar orientation="horizontal" className="sm:hidden" />
|
||||
</ScrollArea>
|
||||
<ScrollArea className="w-64 sm:w-auto">
|
||||
<div className="flex p-2 sm:flex-col">
|
||||
{minutes.map((minute) => (
|
||||
<Button
|
||||
key={minute}
|
||||
size="icon"
|
||||
className={cn(
|
||||
"aspect-square shrink-0 text-label sm:w-full",
|
||||
date && date.getMinutes() === minute
|
||||
? "!bg-primary text-primary-foreground"
|
||||
: "!bg-background",
|
||||
)}
|
||||
onClick={() =>
|
||||
handleTimeChange("minute", minute.toString())
|
||||
}
|
||||
>
|
||||
{minute.toString().padStart(2, "0")}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<ScrollBar orientation="horizontal" className="sm:hidden" />
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export { DateTimePicker };
|
||||
@@ -0,0 +1,331 @@
|
||||
import dayjs from "dayjs";
|
||||
import { useState } from "react";
|
||||
import { type UseFormReturn } from "react-hook-form";
|
||||
import {
|
||||
Checkbox,
|
||||
DatePicker,
|
||||
Input,
|
||||
Label,
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
import { GeneralFileUploader } from "@/widgets/post/ui/general-file-uploader";
|
||||
import { FormWrapper } from "@/widgets/form-wrapper";
|
||||
import { CodeSelect } from "@/entities/code";
|
||||
import { FormItem, FormControl, FormField } from "@/shared/ui/form";
|
||||
import type { ScheduleFormData } from "../model/schedule-interface";
|
||||
import { DateTimePicker } from "./date-time-picker";
|
||||
|
||||
type ScheduleFormProps = {
|
||||
form: UseFormReturn<ScheduleFormData>;
|
||||
scheduleId?: string;
|
||||
handleSave: (data: ScheduleFormData) => Promise<void>;
|
||||
handleDelete?: () => Promise<void>;
|
||||
};
|
||||
|
||||
function ScheduleForm({ form, scheduleId, ...props }: ScheduleFormProps) {
|
||||
const isModifyMode = !!scheduleId;
|
||||
|
||||
const [beginDate, setBeginDate] = useState<Date | undefined>(
|
||||
dayjs(form?.getValues("schdlBgngDt"))?.toDate(),
|
||||
);
|
||||
const [endDate, setEndDate] = useState<Date | undefined>(
|
||||
dayjs(form?.getValues("schdlEndDt"))?.toDate(),
|
||||
);
|
||||
const [isAllDay, setIsAllDay] = useState(false);
|
||||
const [deleteAttachmentIds, setDeleteAttachmentIds] = useState<string[]>([]);
|
||||
|
||||
const handleDateTimeChange =
|
||||
(name: "schdlBgngDt" | "schdlEndDt") => (date?: Date) => {
|
||||
if (!date) return;
|
||||
form.setValue(name, dayjs(date).format("YYYY-MM-DD HH:mm:ss"));
|
||||
setIsAllDay(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormWrapper form={form} isModifyMode={isModifyMode} {...props}>
|
||||
<div className="card">
|
||||
<Table variant="secondary">
|
||||
<colgroup>
|
||||
<col width="15%" />
|
||||
<col width="85%" />
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>제목</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="schdlTtl"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
placeholder="일정 제목을 입력하십시오."
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>일정 기간</TableHead>
|
||||
<TableCell className="border">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="schdlBgngDt"
|
||||
render={({ field }) => (
|
||||
<DateTimePicker
|
||||
date={beginDate}
|
||||
classes={{ trigger: "w-auto px-4 justify-start" }}
|
||||
onDateChange={(date) => {
|
||||
setBeginDate(date);
|
||||
handleDateTimeChange("schdlBgngDt")(date);
|
||||
}}
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
~
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="schdlEndDt"
|
||||
render={({ field }) => (
|
||||
<DateTimePicker
|
||||
date={endDate}
|
||||
classes={{ trigger: "w-auto px-4 justify-start" }}
|
||||
onDateChange={(date) => {
|
||||
setEndDate(date);
|
||||
handleDateTimeChange("schdlEndDt")(date);
|
||||
}}
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="allDay"
|
||||
checked={isAllDay}
|
||||
onCheckedChange={(checked) => {
|
||||
setIsAllDay(checked === true);
|
||||
if (checked === true) {
|
||||
const baseDate = form.getValues("schdlBgngDt");
|
||||
if (baseDate) {
|
||||
const newStartDate = dayjs(baseDate).format(
|
||||
"YYYY-MM-DD 00:00:00",
|
||||
);
|
||||
const newEndDate = dayjs(baseDate).format(
|
||||
"YYYY-MM-DD 23:59:59",
|
||||
);
|
||||
setBeginDate(dayjs(newStartDate).toDate());
|
||||
setEndDate(dayjs(newEndDate).toDate());
|
||||
form.setValue("schdlBgngDt", newStartDate);
|
||||
form.setValue("schdlEndDt", newEndDate);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="allDay">종일</Label>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>일정 반복</TableHead>
|
||||
<TableCell className="border">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reptYn"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<div className="mr-2 flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="reptY"
|
||||
checked={field.value === "Y"}
|
||||
onCheckedChange={(checked) => {
|
||||
field.onChange(checked ? "Y" : "N");
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="reptY">반복</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reptDto.reptCycleCd"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<CodeSelect
|
||||
{...field}
|
||||
upCd="SCHDLRPTCD"
|
||||
value={field.value ?? ""}
|
||||
onValueChange={(value) =>
|
||||
value && field.onChange(value)
|
||||
}
|
||||
placeholder="반복 주기 선택"
|
||||
disabled={form.watch("reptYn") === "N"}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reptDto.reptEndDt"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<DatePicker
|
||||
mode="single"
|
||||
selected={
|
||||
field.value
|
||||
? dayjs(field.value).toDate()
|
||||
: undefined
|
||||
}
|
||||
onSelect={(value) =>
|
||||
value && field.onChange(value)
|
||||
}
|
||||
className="w-auto px-4"
|
||||
placeholder="반복 종료일 선택"
|
||||
disabled={form.watch("reptYn") === "N"}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>일정 유형</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="schdlSeCd"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<CodeSelect
|
||||
{...field}
|
||||
upCd="SCHDLCLCD"
|
||||
value={field.value ?? ""}
|
||||
onValueChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>일정 내용</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="schdlCn"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="min-h-32 !text-[1.3rem]"
|
||||
placeholder="일정 내용을 입력하십시오."
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>대상 사이트</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="commSchdlYn"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="commSchdlY"
|
||||
checked={field.value === "Y"}
|
||||
onCheckedChange={(checked) => {
|
||||
field.onChange(checked ? "Y" : "N");
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="commSchdlY">공용</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>첨부 파일</TableHead>
|
||||
<TableCell className="border">
|
||||
<GeneralFileUploader
|
||||
form={form}
|
||||
deleteAttachmentIds={deleteAttachmentIds}
|
||||
setDeleteAttachmentIds={setDeleteAttachmentIds}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>사용 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="useYn"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
className="flex items-center gap-4"
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="Y" id="useY" />
|
||||
<Label htmlFor="useY">사용</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="N" id="useN" />
|
||||
<Label htmlFor="useN">미사용</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</FormWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export { ScheduleForm };
|
||||
@@ -0,0 +1,63 @@
|
||||
"use client";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import FullCalendar from "@fullcalendar/react";
|
||||
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||
import interactionPlugin, {
|
||||
type DateClickArg,
|
||||
} from "@fullcalendar/interaction";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ENDPOINT, SITE_ID } from "@/shared/config";
|
||||
import { ScheduleApi } from "../api/schedule-service";
|
||||
import type { ScheduleParams } from "../model/schedule-interface";
|
||||
|
||||
function SchedulePage() {
|
||||
const router = useRouter();
|
||||
const [scheduleParams, setScheduleParams] = useState<ScheduleParams>({
|
||||
siteId: SITE_ID,
|
||||
schdlBgngYmd: "",
|
||||
schdlEndYmd: "",
|
||||
useYn: "Y",
|
||||
});
|
||||
|
||||
const { data } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.SCHEDULES, scheduleParams],
|
||||
queryFn: () => ScheduleApi.getSchedules(scheduleParams),
|
||||
select: (data) => {
|
||||
return data.map((item) => ({
|
||||
title: item.schdlTtl,
|
||||
start: item.schdlBgngDt,
|
||||
end: item.schdlEndDt,
|
||||
}));
|
||||
},
|
||||
enabled: !!scheduleParams.schdlBgngYmd && !!scheduleParams.schdlEndYmd,
|
||||
});
|
||||
|
||||
const handleDateClick = (info: DateClickArg) => {
|
||||
router.push(`/schedule/register?dateStr=${info.dateStr}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card card-border">
|
||||
<div className="p-5">
|
||||
<FullCalendar
|
||||
plugins={[dayGridPlugin, interactionPlugin]}
|
||||
events={data}
|
||||
locale="ko"
|
||||
dateClick={handleDateClick}
|
||||
datesSet={(dateInfo) => {
|
||||
setScheduleParams({
|
||||
...scheduleParams,
|
||||
schdlBgngYmd: dayjs(dateInfo.startStr).format("YYYY-MM-DD"),
|
||||
schdlEndYmd: dayjs(dateInfo.endStr).format("YYYY-MM-DD"),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { SchedulePage };
|
||||
@@ -39,6 +39,7 @@ export const ENDPOINT = {
|
||||
LWPO_CODE: "/cms-service/api/v1/lwpo-cds",
|
||||
UP_CODE: "/cms-service/api/v1/up-codes",
|
||||
POPUPS: "/cms-service/api/v1/popups",
|
||||
SCHEDULES: "/cms-service/api/v1/schdls",
|
||||
},
|
||||
BOARD_SERVICE: {
|
||||
BBS: "/board-service/api/v1/bbs",
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"@radix-ui/react-popover": "^1.1.6",
|
||||
"@radix-ui/react-progress": "^1.1.2",
|
||||
"@radix-ui/react-radio-group": "^1.2.2",
|
||||
"@radix-ui/react-scroll-area": "^1.2.3",
|
||||
"@radix-ui/react-select": "^2.1.6",
|
||||
"@radix-ui/react-separator": "^1.1.2",
|
||||
"@radix-ui/react-slider": "^1.2.3",
|
||||
|
||||
@@ -9,21 +9,30 @@ import { Button } from "../button";
|
||||
import { Calendar } from "../calendar";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "../popover";
|
||||
|
||||
type DatePickerProps = DayPickerSingleProps;
|
||||
type DatePickerProps = {
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
} & DayPickerSingleProps;
|
||||
|
||||
export function DatePicker({ className, ...props }: DatePickerProps) {
|
||||
export function DatePicker({
|
||||
className,
|
||||
placeholder = "날짜 선택",
|
||||
disabled,
|
||||
...props
|
||||
}: DatePickerProps) {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cn("date-picker-trigger", className)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<CalendarIcon className="!size-[1.6rem]" />
|
||||
{props.selected ? (
|
||||
format(props.selected, "yyyy-MM-dd")
|
||||
) : (
|
||||
<span>날짜 선택</span>
|
||||
<span>{placeholder}</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
|
||||
@@ -20,6 +20,7 @@ export * from "./page-title";
|
||||
export * from "./popover";
|
||||
export * from "./progress";
|
||||
export * from "./radio-group";
|
||||
export * from "./scroll-area";
|
||||
export * from "./select";
|
||||
export * from "./slider";
|
||||
export * from "./skeleton";
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
));
|
||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
|
||||
|
||||
const ScrollBar = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
>(({ className, orientation = "vertical", ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
ref={ref}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"flex touch-none select-none transition-colors",
|
||||
orientation === "vertical" &&
|
||||
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||
orientation === "horizontal" &&
|
||||
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
));
|
||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
|
||||
|
||||
export { ScrollArea, ScrollBar };
|
||||
Generated
+56
@@ -178,6 +178,18 @@ importers:
|
||||
'@frontend-opensource/use-react-hooks':
|
||||
specifier: ^1.1.4
|
||||
version: 1.1.4
|
||||
'@fullcalendar/core':
|
||||
specifier: ^6.1.17
|
||||
version: 6.1.17
|
||||
'@fullcalendar/daygrid':
|
||||
specifier: ^6.1.17
|
||||
version: 6.1.17(@fullcalendar/core@6.1.17)
|
||||
'@fullcalendar/interaction':
|
||||
specifier: ^6.1.17
|
||||
version: 6.1.17(@fullcalendar/core@6.1.17)
|
||||
'@fullcalendar/react':
|
||||
specifier: ^6.1.17
|
||||
version: 6.1.17(@fullcalendar/core@6.1.17)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@hookform/resolvers':
|
||||
specifier: ^4.1.3
|
||||
version: 4.1.3(react-hook-form@7.54.2)
|
||||
@@ -214,6 +226,9 @@ importers:
|
||||
js-cookie:
|
||||
specifier: ^3.0.5
|
||||
version: 3.0.5
|
||||
lucide-react:
|
||||
specifier: ^0.474.0
|
||||
version: 0.474.0(react@18.3.1)
|
||||
next:
|
||||
specifier: 15.3.0
|
||||
version: 15.3.0(react-dom@18.3.1)(react@18.3.1)
|
||||
@@ -538,6 +553,9 @@ importers:
|
||||
'@radix-ui/react-radio-group':
|
||||
specifier: ^1.2.2
|
||||
version: 1.2.3(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-scroll-area':
|
||||
specifier: ^1.2.3
|
||||
version: 1.2.3(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-select':
|
||||
specifier: ^2.1.6
|
||||
version: 2.1.6(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
@@ -1756,6 +1774,40 @@ packages:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@fullcalendar/core@6.1.17:
|
||||
resolution: {integrity: sha512-0W7lnIrv18ruJ5zeWBeNZXO8qCWlzxDdp9COFEsZnyNjiEhUVnrW/dPbjRKYpL0edGG0/Lhs0ghp1z/5ekt8ZA==}
|
||||
dependencies:
|
||||
preact: 10.12.1
|
||||
dev: false
|
||||
|
||||
/@fullcalendar/daygrid@6.1.17(@fullcalendar/core@6.1.17):
|
||||
resolution: {integrity: sha512-K7m+pd7oVJ9fW4h7CLDdDGJbc9szJ1xDU1DZ2ag+7oOo1aCNLv44CehzkkknM6r8EYlOOhgaelxQpKAI4glj7A==}
|
||||
peerDependencies:
|
||||
'@fullcalendar/core': ~6.1.17
|
||||
dependencies:
|
||||
'@fullcalendar/core': 6.1.17
|
||||
dev: false
|
||||
|
||||
/@fullcalendar/interaction@6.1.17(@fullcalendar/core@6.1.17):
|
||||
resolution: {integrity: sha512-AudvQvgmJP2FU89wpSulUUjeWv24SuyCx8FzH2WIPVaYg+vDGGYarI7K6PcM3TH7B/CyaBjm5Rqw9lXgnwt5YA==}
|
||||
peerDependencies:
|
||||
'@fullcalendar/core': ~6.1.17
|
||||
dependencies:
|
||||
'@fullcalendar/core': 6.1.17
|
||||
dev: false
|
||||
|
||||
/@fullcalendar/react@6.1.17(@fullcalendar/core@6.1.17)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-AA8soHhlfRH5dUeqHnfAtzDiXa2vrgWocJSK/F5qzw/pOxc9MqpuoS/nQBROWtHHg6yQUg3DoGqOOhi7dmylXQ==}
|
||||
peerDependencies:
|
||||
'@fullcalendar/core': ~6.1.17
|
||||
react: ^18.3.1
|
||||
react-dom: ^18.3.1
|
||||
dependencies:
|
||||
'@fullcalendar/core': 6.1.17
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@hookform/resolvers@4.1.3(react-hook-form@7.54.2):
|
||||
resolution: {integrity: sha512-Jsv6UOWYTrEFJ/01ZrnwVXs7KDvP8XIo115i++5PWvNkNvkrsTfGiLS6w+eJ57CYtUtDQalUWovCZDHFJ8u1VQ==}
|
||||
peerDependencies:
|
||||
@@ -7665,6 +7717,10 @@ packages:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
/preact@10.12.1:
|
||||
resolution: {integrity: sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==}
|
||||
dev: false
|
||||
|
||||
/precinct@12.2.0:
|
||||
resolution: {integrity: sha512-NFBMuwIfaJ4SocE9YXPU/n4AcNSoFMVFjP72nvl3cx69j/ke61/hPOWFREVxLkFhhEGnA8ZuVfTqJBa+PK3b5w==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
Reference in New Issue
Block a user