Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend
This commit is contained in:
@@ -26,7 +26,7 @@ export const commentFormSchema = z.object({
|
||||
cmntCn: z.string(),
|
||||
wrtrNm: z.string().nullish(),
|
||||
cmntPswd: z.string(),
|
||||
cmntEvl: z.number().nullish(),
|
||||
cmntEvl: z.string().nullish(),
|
||||
});
|
||||
|
||||
export const commentUpdateFormSchema = commentFormSchema.omit({
|
||||
|
||||
@@ -311,7 +311,11 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="Y" id="cmntUseY" />
|
||||
<Label htmlFor="cmntUseY">사용</Label>
|
||||
<Label htmlFor="cmntUseY">일반댓글</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="C" id="cmntUseC" />
|
||||
<Label htmlFor="cmntUseC">평가댓글</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="N" id="cmntUseN" />
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||
import { keepPreviousData, useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
import { Button, Input, Label } from "@common/components";
|
||||
import { LwpoCodeApi, type LwpoCodeParams } from "@/entities/code";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { useAlertStore, useDataTable } from "@/shared/lib";
|
||||
import { columns } from "../model/lwpocode-table-columns";
|
||||
import { LwpoCodeManagementDialog } from "./lwpocode-dialog";
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
|
||||
type LwpoCodeSectionProps = {
|
||||
upCd?: string;
|
||||
};
|
||||
|
||||
function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [lwpoCodeQuery, setLwpoCodeQuery] = useState<LwpoCodeParams>({
|
||||
lwpoCdNm: "",
|
||||
@@ -36,6 +36,13 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
|
||||
const { mutateAsync: codeRefreshAll } = useMutation({
|
||||
mutationFn: LwpoCodeApi.codeRefreshAll,
|
||||
onSuccess: () => {
|
||||
alert("코드 갱신이 완료되었습니다.");
|
||||
},
|
||||
});
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
query,
|
||||
tableOptions: {
|
||||
@@ -59,20 +66,6 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
|
||||
setPagination({ ...pagination, pageIndex: 0 });
|
||||
}, [upCd]);
|
||||
|
||||
const { mutateAsync: codeRefreshAll } = useMutation({
|
||||
mutationFn: () => LwpoCodeApi.codeRefreshAll(),
|
||||
onSuccess: () => {
|
||||
alert('저장되었습니다.')
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('에러 발생:', error)
|
||||
alert('에러가 발생했습니다.')
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex-1">
|
||||
<div className="card card-border">
|
||||
@@ -98,17 +91,17 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
|
||||
<div className="card card-border">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">하위코드 목록</h3>
|
||||
<button onClick={() => codeRefreshAll()} className="btn">
|
||||
코드 리프레시
|
||||
</button>
|
||||
|
||||
<LwpoCodeManagementDialog
|
||||
open={dialogOpen}
|
||||
onOpenChange={setDialogOpen}
|
||||
>
|
||||
<Button>등록</Button>
|
||||
</LwpoCodeManagementDialog>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button color="emerald" onClick={() => codeRefreshAll()}>
|
||||
코드갱신
|
||||
</Button>
|
||||
<LwpoCodeManagementDialog
|
||||
open={dialogOpen}
|
||||
onOpenChange={setDialogOpen}
|
||||
>
|
||||
<Button>등록</Button>
|
||||
</LwpoCodeManagementDialog>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card !m-4 h-[calc(100vh-35rem)] overflow-auto !p-0">
|
||||
{tableComponent}
|
||||
|
||||
@@ -64,12 +64,7 @@ function DateTimePicker({ date, classes, onDateChange }: DateTimePickerProps) {
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0">
|
||||
<div className="sm:flex">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={handleDateSelect}
|
||||
initialFocus
|
||||
/>
|
||||
<Calendar mode="single" selected={date} onSelect={handleDateSelect} />
|
||||
<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">
|
||||
|
||||
@@ -4,7 +4,14 @@ import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { Button, Input, Textarea } from "@common/components";
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Label,
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
import {
|
||||
CommentApi,
|
||||
commentFormSchema,
|
||||
@@ -14,6 +21,7 @@ import {
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import {
|
||||
useAlertStore,
|
||||
useMenuStore,
|
||||
useMutationWrapper,
|
||||
usePrompt,
|
||||
useUserStore,
|
||||
@@ -24,10 +32,11 @@ function PostComment({ bbsId, pstNo }: { bbsId: number; pstNo: number }) {
|
||||
const { prompt } = usePrompt();
|
||||
const { confirm } = useConfirm();
|
||||
const queryClient = useQueryClient();
|
||||
const bbsInfo = useMenuStore((state) => state.bbsInfo);
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const [editValue, setEditValue] = useState("");
|
||||
const [editingCmntNo, setEditingCmntNo] = useState<number | null>(null);
|
||||
const [editValue, setEditValue] = useState<string>("");
|
||||
|
||||
const { data: comments } = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS, pstNo],
|
||||
@@ -123,6 +132,46 @@ function PostComment({ bbsId, pstNo }: { bbsId: number; pstNo: number }) {
|
||||
<div className="card card-border !p-8">
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
{bbsInfo?.cmntUseYn === "C" && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cmntEvl"
|
||||
render={({ field }) => (
|
||||
<FormItem className="mb-4">
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
name="cmntEvl"
|
||||
value={field.value?.toString() ?? ""}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="5" id="cmntEvl5" />
|
||||
<Label htmlFor="cmntEvl5">매우만족 ★★★★★</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="4" id="cmntEvl4" />
|
||||
<Label htmlFor="cmntEvl4">만족 ★★★★</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="3" id="cmntEvl3" />
|
||||
<Label htmlFor="cmntEvl3">보통 ★★★</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="2" id="cmntEvl2" />
|
||||
<Label htmlFor="cmntEvl2">불만족 ★★</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="1" id="cmntEvl1" />
|
||||
<Label htmlFor="cmntEvl1">매우불만족 ★</Label>
|
||||
</div>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cmntCn"
|
||||
@@ -188,71 +237,80 @@ function PostComment({ bbsId, pstNo }: { bbsId: number; pstNo: number }) {
|
||||
if (comment.delYn === "Y") return null;
|
||||
return (
|
||||
<div
|
||||
className="flex justify-between gap-2 border-t border-gray-200 py-8"
|
||||
key={comment.cmntNo}
|
||||
className="flex flex-col border-t border-gray-200 py-8"
|
||||
>
|
||||
<div className="flex w-full gap-10">
|
||||
<div className="flex w-[10rem] shrink-0 flex-col">
|
||||
<span className="text-[1.4rem] font-bold text-label">
|
||||
{comment.wrtrNm}
|
||||
</span>
|
||||
<span className="text-[1.2rem] text-label">
|
||||
{dayjs(comment.regDt).format("YYYY.MM.DD")}
|
||||
</span>
|
||||
</div>
|
||||
{editingCmntNo === comment.cmntNo ? (
|
||||
<div className="flex flex-1 flex-col gap-2">
|
||||
<Textarea
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
maxLength={1000}
|
||||
className="h-[10rem] bg-background p-4 !text-[1.3rem] !leading-6"
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
handleUpdateComment(comment.cmntNo, comment.wrtrNm)
|
||||
}
|
||||
>
|
||||
저장
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
color="gray"
|
||||
onClick={() => {
|
||||
setEditingCmntNo(null);
|
||||
setEditValue("");
|
||||
}}
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="break-all text-[1.4rem] text-foreground">
|
||||
{comment.cmntCn}
|
||||
</p>
|
||||
<div>
|
||||
{Array.from({ length: Number(comment.cmntEvl) }).map(
|
||||
(_, index) => (
|
||||
<span key={index}>★</span>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2 whitespace-nowrap">
|
||||
<Button
|
||||
size="icon"
|
||||
color="white"
|
||||
onClick={() => {
|
||||
setEditingCmntNo(comment.cmntNo);
|
||||
setEditValue(comment.cmntCn);
|
||||
}}
|
||||
>
|
||||
<i className="diveicon di-pencil" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
color="white"
|
||||
onClick={() => handleDeleteComment(comment.cmntNo)}
|
||||
>
|
||||
<i className="diveicon di-trash" />
|
||||
</Button>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex w-full gap-10">
|
||||
<div className="flex w-[10rem] shrink-0 flex-col">
|
||||
<span className="text-[1.4rem] font-bold text-label">
|
||||
{comment.wrtrNm}
|
||||
</span>
|
||||
<span className="text-[1.2rem] text-label">
|
||||
{dayjs(comment.regDt).format("YYYY.MM.DD")}
|
||||
</span>
|
||||
</div>
|
||||
{editingCmntNo === comment.cmntNo ? (
|
||||
<div className="flex flex-1 flex-col gap-2">
|
||||
<Textarea
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
maxLength={1000}
|
||||
className="h-[10rem] bg-background p-4 !text-[1.3rem] !leading-6"
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
handleUpdateComment(comment.cmntNo, comment.wrtrNm)
|
||||
}
|
||||
>
|
||||
저장
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
color="gray"
|
||||
onClick={() => {
|
||||
setEditingCmntNo(null);
|
||||
setEditValue("");
|
||||
}}
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="break-all text-[1.4rem] text-foreground">
|
||||
{comment.cmntCn}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2 whitespace-nowrap">
|
||||
<Button
|
||||
size="icon"
|
||||
color="white"
|
||||
onClick={() => {
|
||||
setEditingCmntNo(comment.cmntNo);
|
||||
setEditValue(comment.cmntCn);
|
||||
}}
|
||||
>
|
||||
<i className="diveicon di-pencil" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
color="white"
|
||||
onClick={() => handleDeleteComment(comment.cmntNo)}
|
||||
>
|
||||
<i className="diveicon di-trash" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
"js-cookie": "^3.0.5",
|
||||
"lucide-react": "^0.474.0",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-day-picker": "^9.7.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"tailwind-merge": "^3.0.1",
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "../../lib";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
|
||||
export { Button, buttonVariants };
|
||||
@@ -1,69 +1,211 @@
|
||||
import * as React from "react";
|
||||
import { ko } from "date-fns/locale";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { DayPicker } from "react-day-picker";
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
} from "lucide-react";
|
||||
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker";
|
||||
import { ko } from "react-day-picker/locale";
|
||||
|
||||
import { cn } from "../../lib/utils";
|
||||
import { buttonVariants } from "../button";
|
||||
import { cn } from "../../lib";
|
||||
import { Button, buttonVariants } from "./button";
|
||||
|
||||
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
|
||||
|
||||
export function Calendar({
|
||||
function Calendar({
|
||||
className,
|
||||
classNames,
|
||||
showOutsideDays = true,
|
||||
captionLayout = "dropdown",
|
||||
buttonVariant = "ghost",
|
||||
formatters,
|
||||
components,
|
||||
...props
|
||||
}: CalendarProps) {
|
||||
}: React.ComponentProps<typeof DayPicker> & {
|
||||
buttonVariant?: React.ComponentProps<typeof Button>["variant"];
|
||||
}) {
|
||||
const defaultClassNames = getDefaultClassNames();
|
||||
|
||||
return (
|
||||
<DayPicker
|
||||
locale={ko}
|
||||
showOutsideDays={showOutsideDays}
|
||||
className={cn("p-[1.2rem]", className)}
|
||||
className={cn(
|
||||
"group/calendar bg-background p-3 [--cell-size:2.5rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
|
||||
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
||||
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
||||
className,
|
||||
)}
|
||||
captionLayout={captionLayout}
|
||||
formatters={{
|
||||
formatMonthDropdown: (date) =>
|
||||
date.toLocaleString("default", { month: "short" }),
|
||||
...formatters,
|
||||
}}
|
||||
classNames={{
|
||||
months:
|
||||
"flex flex-col sm:flex-row space-y-[1.6rem] sm:space-x-[1.6rem] sm:space-y-0",
|
||||
month: "space-y-[1.6rem]",
|
||||
caption: "flex justify-center pt-[0.4rem] relative items-center",
|
||||
caption_label: "text-[1.4rem] font-medium",
|
||||
nav: "space-x-[0.4rem] flex items-center",
|
||||
nav_button: cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"size-[2.8rem] bg-transparent p-0 opacity-50 hover:opacity-100",
|
||||
root: cn("w-fit", defaultClassNames.root),
|
||||
months: cn(
|
||||
"relative flex flex-col gap-4 md:flex-row",
|
||||
defaultClassNames.months,
|
||||
),
|
||||
month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
|
||||
nav: cn(
|
||||
"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
|
||||
defaultClassNames.nav,
|
||||
),
|
||||
button_previous: cn(
|
||||
buttonVariants({ variant: buttonVariant }),
|
||||
"h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50 [&_svg]:size-6",
|
||||
defaultClassNames.button_previous,
|
||||
),
|
||||
button_next: cn(
|
||||
buttonVariants({ variant: buttonVariant }),
|
||||
"h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50 [&_svg]:size-6",
|
||||
defaultClassNames.button_next,
|
||||
),
|
||||
month_caption: cn(
|
||||
"flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]",
|
||||
defaultClassNames.month_caption,
|
||||
),
|
||||
dropdowns: cn(
|
||||
"flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-xl font-medium",
|
||||
captionLayout === "dropdown" && "flex-row-reverse",
|
||||
defaultClassNames.dropdowns,
|
||||
),
|
||||
dropdown_root: cn(
|
||||
"has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border",
|
||||
defaultClassNames.dropdown_root,
|
||||
),
|
||||
dropdown: cn("absolute inset-0 opacity-0", defaultClassNames.dropdown),
|
||||
caption_label: cn(
|
||||
"select-none font-medium",
|
||||
captionLayout === "label"
|
||||
? "text-xl"
|
||||
: "[&>svg]:text-muted-foreground flex h-10 items-center gap-1 rounded-md pl-4 pr-3 text-xl [&>svg]:size-3.5",
|
||||
defaultClassNames.caption_label,
|
||||
),
|
||||
table: "w-full border-collapse",
|
||||
weekdays: cn("flex", defaultClassNames.weekdays),
|
||||
weekday: cn(
|
||||
"text-muted-foreground flex-1 select-none rounded-md text-xl font-normal",
|
||||
defaultClassNames.weekday,
|
||||
),
|
||||
week: cn("mt-2 flex w-full", defaultClassNames.week),
|
||||
week_number_header: cn(
|
||||
"w-[--cell-size] select-none",
|
||||
defaultClassNames.week_number_header,
|
||||
),
|
||||
week_number: cn(
|
||||
"text-muted-foreground select-none text-[0.8rem]",
|
||||
defaultClassNames.week_number,
|
||||
),
|
||||
nav_button_previous: "absolute left-[0.4rem]",
|
||||
nav_button_next: "absolute right-[0.4rem]",
|
||||
table: "w-full border-collapse space-y-[0.4rem]",
|
||||
head_row: "flex",
|
||||
head_cell:
|
||||
"text-muted-foreground rounded-md w-[3.6rem] font-normal text-[1.28rem]",
|
||||
row: "flex w-full mt-[0.8rem]",
|
||||
cell: "size-[3.6rem] text-center text-[1.4rem] p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
|
||||
day: cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"size-[3.6rem] p-0 font-normal aria-selected:opacity-100 text-[1.3rem]",
|
||||
"group/day relative aspect-square h-full w-full size-12 select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md",
|
||||
defaultClassNames.day,
|
||||
),
|
||||
day_range_end: "day-range-end",
|
||||
day_selected:
|
||||
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
||||
day_today: "bg-accent text-accent-foreground",
|
||||
day_outside:
|
||||
"day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
|
||||
day_disabled: "text-muted-foreground opacity-50",
|
||||
day_range_middle:
|
||||
"aria-selected:bg-accent aria-selected:text-accent-foreground",
|
||||
day_hidden: "invisible",
|
||||
range_start: cn(
|
||||
"bg-accent rounded-l-md",
|
||||
defaultClassNames.range_start,
|
||||
),
|
||||
range_middle: cn("rounded-none", defaultClassNames.range_middle),
|
||||
range_end: cn("bg-accent rounded-r-md", defaultClassNames.range_end),
|
||||
today: cn(
|
||||
"bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
|
||||
defaultClassNames.today,
|
||||
),
|
||||
outside: cn(
|
||||
"text-muted-foreground aria-selected:text-muted-foreground",
|
||||
defaultClassNames.outside,
|
||||
),
|
||||
disabled: cn(
|
||||
"text-muted-foreground opacity-50",
|
||||
defaultClassNames.disabled,
|
||||
),
|
||||
hidden: cn("invisible", defaultClassNames.hidden),
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
IconLeft: ({ className, ...props }) => (
|
||||
<ChevronLeft className={cn("size-[1.6rem]", className)} {...props} />
|
||||
),
|
||||
IconRight: ({ className, ...props }) => (
|
||||
<ChevronRight className={cn("size-[1.6rem]", className)} {...props} />
|
||||
),
|
||||
Root: ({ className, rootRef, ...props }) => {
|
||||
return (
|
||||
<div
|
||||
data-slot="calendar"
|
||||
ref={rootRef}
|
||||
className={cn(className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
Chevron: ({ className, orientation, ...props }) => {
|
||||
if (orientation === "left") {
|
||||
return (
|
||||
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
if (orientation === "right") {
|
||||
return (
|
||||
<ChevronRightIcon
|
||||
className={cn("size-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ChevronDownIcon className={cn("size-4", className)} {...props} />
|
||||
);
|
||||
},
|
||||
DayButton: CalendarDayButton,
|
||||
WeekNumber: ({ children, ...props }) => {
|
||||
return (
|
||||
<td {...props}>
|
||||
<div className="flex size-[--cell-size] items-center justify-center text-center">
|
||||
{children}
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
},
|
||||
...components,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Calendar.displayName = "Calendar";
|
||||
|
||||
function CalendarDayButton({
|
||||
className,
|
||||
day,
|
||||
modifiers,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DayButton>) {
|
||||
const defaultClassNames = getDefaultClassNames();
|
||||
|
||||
const ref = React.useRef<HTMLButtonElement>(null);
|
||||
React.useEffect(() => {
|
||||
if (modifiers.focused) ref.current?.focus();
|
||||
}, [modifiers.focused]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
data-day={day.date.toLocaleDateString()}
|
||||
data-selected-single={
|
||||
modifiers.selected &&
|
||||
!modifiers.range_start &&
|
||||
!modifiers.range_end &&
|
||||
!modifiers.range_middle
|
||||
}
|
||||
data-range-start={modifiers.range_start}
|
||||
data-range-end={modifiers.range_end}
|
||||
data-range-middle={modifiers.range_middle}
|
||||
className={cn(
|
||||
"group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-[--cell-size] flex-col gap-1 text-xl font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-end=true]:bg-primary data-[range-middle=true]:bg-accent data-[range-start=true]:bg-primary data-[selected-single=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:text-accent-foreground data-[range-start=true]:text-primary-foreground data-[selected-single=true]:text-primary-foreground group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70",
|
||||
defaultClassNames.day,
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { Calendar, CalendarDayButton };
|
||||
|
||||
@@ -2,7 +2,7 @@ import "./date-picker.css";
|
||||
|
||||
import { format } from "date-fns";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import type { DayPickerSingleProps } from "react-day-picker";
|
||||
import type { DayPicker } from "react-day-picker";
|
||||
|
||||
import { cn } from "../../lib/utils";
|
||||
import { Button } from "../button";
|
||||
@@ -12,7 +12,8 @@ import { Popover, PopoverContent, PopoverTrigger } from "../popover";
|
||||
type DatePickerProps = {
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
} & DayPickerSingleProps;
|
||||
selected?: Date;
|
||||
} & React.ComponentProps<typeof DayPicker>;
|
||||
|
||||
export function DatePicker({
|
||||
className,
|
||||
@@ -37,7 +38,7 @@ export function DatePicker({
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0">
|
||||
<Calendar initialFocus classNames={{ cell: "rounded-md" }} {...props} />
|
||||
<Calendar classNames={{ cell: "rounded-md" }} {...props} />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
@@ -3,19 +3,20 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { format } from "date-fns";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import type { DateRange } from "react-day-picker";
|
||||
import type { DateRange, DayPicker } from "react-day-picker";
|
||||
|
||||
import { cn } from "../../lib/utils";
|
||||
import { Button } from "../button";
|
||||
import { Calendar } from "../calendar";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "../popover";
|
||||
|
||||
type DateRangePickerProps = React.HTMLAttributes<HTMLDivElement> & {
|
||||
value?: DateRange;
|
||||
defaultValue?: DateRange;
|
||||
disabled?: boolean;
|
||||
onValueChange?: (date: DateRange | undefined) => void;
|
||||
};
|
||||
type DateRangePickerProps = React.HTMLAttributes<HTMLDivElement> &
|
||||
React.ComponentProps<typeof DayPicker> & {
|
||||
value?: DateRange;
|
||||
defaultValue?: DateRange;
|
||||
disabled?: boolean;
|
||||
onValueChange?: (date: DateRange | undefined) => void;
|
||||
};
|
||||
|
||||
export function DateRangePicker({
|
||||
value,
|
||||
@@ -24,6 +25,7 @@ export function DateRangePicker({
|
||||
className,
|
||||
children,
|
||||
onValueChange,
|
||||
...props
|
||||
}: DateRangePickerProps) {
|
||||
const [dateState, setDateState] = useState<DateRange | undefined>(
|
||||
defaultValue,
|
||||
@@ -77,7 +79,7 @@ export function DateRangePicker({
|
||||
<div>
|
||||
{children}
|
||||
<Calendar
|
||||
initialFocus
|
||||
{...props}
|
||||
mode="range"
|
||||
defaultMonth={dateValue?.from}
|
||||
selected={dateValue}
|
||||
|
||||
@@ -94,9 +94,6 @@ export function Header({ viewOnly }: { viewOnly?: boolean }) {
|
||||
<header className="sticky top-0 z-30 box-border flex h-[5rem] w-full items-center justify-between border-b border-solid border-border bg-background">
|
||||
<div className="flex items-center px-6">
|
||||
<SidebarTrigger className="size-12 !bg-transparent p-2 hover:!bg-accent [&>svg]:!size-full [&>svg]:text-foreground" />
|
||||
<div className="MobMenuBtn">
|
||||
<img src={images.menuBtn} />
|
||||
</div>
|
||||
<h1 className="flex items-center gap-4">
|
||||
<img src={images.logo} className="h-[3rem]" alt="logo" />
|
||||
<span className="text-[1.8rem] font-semibold text-foreground">
|
||||
|
||||
Generated
+27
-3
@@ -759,8 +759,8 @@ importers:
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0(chart.js@4.4.8)(react@18.3.1)
|
||||
react-day-picker:
|
||||
specifier: ^8.10.1
|
||||
version: 8.10.1(date-fns@3.6.0)(react@18.3.1)
|
||||
specifier: ^9.7.0
|
||||
version: 9.7.0(react@18.3.1)
|
||||
react-dnd:
|
||||
specifier: ^16.0.1
|
||||
version: 16.0.1(@types/node@22.13.5)(@types/react@18.3.18)(react@18.3.1)
|
||||
@@ -1109,6 +1109,10 @@ packages:
|
||||
sisteransi: 1.0.5
|
||||
dev: true
|
||||
|
||||
/@date-fns/tz@1.2.0:
|
||||
resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==}
|
||||
dev: false
|
||||
|
||||
/@dependents/detective-less@5.0.1:
|
||||
resolution: {integrity: sha512-Y6+WUMsTFWE5jb20IFP4YGa5IrGY/+a/FbOSjDF/wz9gepU2hwCYSXRHP/vPwBvwcY3SVMASt4yXxbXNXigmZQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -2481,7 +2485,7 @@ packages:
|
||||
dnd-multi-backend: 8.1.2(dnd-core@16.0.1)
|
||||
framer-motion: 11.18.2(react-dom@18.3.1)(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dnd: 16.0.1(@types/node@22.13.9)(@types/react@18.3.18)(react@18.3.1)
|
||||
react-dnd: 16.0.1(@types/node@22.13.5)(@types/react@18.3.18)(react@18.3.1)
|
||||
react-dnd-html5-backend: 16.0.1
|
||||
react-dnd-touch-backend: 16.0.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
@@ -5526,10 +5530,18 @@ packages:
|
||||
is-data-view: 1.0.2
|
||||
dev: true
|
||||
|
||||
/date-fns-jalali@4.1.0-0:
|
||||
resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==}
|
||||
dev: false
|
||||
|
||||
/date-fns@3.6.0:
|
||||
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
|
||||
dev: false
|
||||
|
||||
/date-fns@4.1.0:
|
||||
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
|
||||
dev: false
|
||||
|
||||
/dayjs@1.11.13:
|
||||
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
|
||||
dev: false
|
||||
@@ -8257,6 +8269,18 @@ packages:
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/react-day-picker@9.7.0(react@18.3.1):
|
||||
resolution: {integrity: sha512-urlK4C9XJZVpQ81tmVgd2O7lZ0VQldZeHzNejbwLWZSkzHH498KnArT0EHNfKBOWwKc935iMLGZdxXPRISzUxQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
react: ^18.3.1
|
||||
dependencies:
|
||||
'@date-fns/tz': 1.2.0
|
||||
date-fns: 4.1.0
|
||||
date-fns-jalali: 4.1.0-0
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/react-dnd-html5-backend@16.0.1:
|
||||
resolution: {integrity: sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==}
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user