From 47cbdf5fa9ff87def6fcc94dc4e45453d1d0f76e Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 27 Jun 2025 10:31:13 +0900 Subject: [PATCH 1/3] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EC=BD=94=EB=93=9C=EA=B0=B1=EC=8B=A0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20W3=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cs/src/pages/code/ui/lwpocode-section.tsx | 49 ++++++++----------- .../common/components/src/ui/header/index.tsx | 3 -- 2 files changed, 21 insertions(+), 31 deletions(-) diff --git a/apps/cs/src/pages/code/ui/lwpocode-section.tsx b/apps/cs/src/pages/code/ui/lwpocode-section.tsx index 78a51fae5..b1713fa0f 100644 --- a/apps/cs/src/pages/code/ui/lwpocode-section.tsx +++ b/apps/cs/src/pages/code/ui/lwpocode-section.tsx @@ -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({ 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 (
@@ -98,17 +91,17 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {

하위코드 목록

- - - - - - +
+ + + + +
{tableComponent} diff --git a/packages/common/components/src/ui/header/index.tsx b/packages/common/components/src/ui/header/index.tsx index 8457b28dd..84c27f1c4 100644 --- a/packages/common/components/src/ui/header/index.tsx +++ b/packages/common/components/src/ui/header/index.tsx @@ -94,9 +94,6 @@ export function Header({ viewOnly }: { viewOnly?: boolean }) {
-
- -

logo From 2b9028492b3014cd92daccb138fa2328b5336595 Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 27 Jun 2025 17:45:10 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20Calendar=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=EC=97=90=20=EC=97=B0=EB=8F=84=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/schedule/ui/date-time-picker.tsx | 7 +- packages/common/components/package.json | 2 +- .../components/src/ui/calendar/button.tsx | 56 +++++ .../components/src/ui/calendar/index.tsx | 234 ++++++++++++++---- .../components/src/ui/date-picker/index.tsx | 7 +- .../src/ui/date-range-picker/index.tsx | 18 +- pnpm-lock.yaml | 30 ++- 7 files changed, 287 insertions(+), 67 deletions(-) create mode 100644 packages/common/components/src/ui/calendar/button.tsx diff --git a/apps/cs/src/pages/schedule/ui/date-time-picker.tsx b/apps/cs/src/pages/schedule/ui/date-time-picker.tsx index 0302253b9..8906585a0 100644 --- a/apps/cs/src/pages/schedule/ui/date-time-picker.tsx +++ b/apps/cs/src/pages/schedule/ui/date-time-picker.tsx @@ -64,12 +64,7 @@ function DateTimePicker({ date, classes, onDateChange }: DateTimePickerProps) {
- +
diff --git a/packages/common/components/package.json b/packages/common/components/package.json index 925699de0..0edd7211b 100644 --- a/packages/common/components/package.json +++ b/packages/common/components/package.json @@ -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", diff --git a/packages/common/components/src/ui/calendar/button.tsx b/packages/common/components/src/ui/calendar/button.tsx new file mode 100644 index 000000000..add67a757 --- /dev/null +++ b/packages/common/components/src/ui/calendar/button.tsx @@ -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, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/packages/common/components/src/ui/calendar/index.tsx b/packages/common/components/src/ui/calendar/index.tsx index c1a6d5204..f2af829e5 100644 --- a/packages/common/components/src/ui/calendar/index.tsx +++ b/packages/common/components/src/ui/calendar/index.tsx @@ -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; - -export function Calendar({ +function Calendar({ className, classNames, showOutsideDays = true, + captionLayout = "dropdown", + buttonVariant = "ghost", + formatters, + components, ...props -}: CalendarProps) { +}: React.ComponentProps & { + buttonVariant?: React.ComponentProps["variant"]; +}) { + const defaultClassNames = getDefaultClassNames(); + return ( 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 }) => ( - - ), - IconRight: ({ className, ...props }) => ( - - ), + Root: ({ className, rootRef, ...props }) => { + return ( +
+ ); + }, + Chevron: ({ className, orientation, ...props }) => { + if (orientation === "left") { + return ( + + ); + } + + if (orientation === "right") { + return ( + + ); + } + + return ( + + ); + }, + DayButton: CalendarDayButton, + WeekNumber: ({ children, ...props }) => { + return ( + +
+ {children} +
+ + ); + }, + ...components, }} {...props} /> ); } -Calendar.displayName = "Calendar"; + +function CalendarDayButton({ + className, + day, + modifiers, + ...props +}: React.ComponentProps) { + const defaultClassNames = getDefaultClassNames(); + + const ref = React.useRef(null); + React.useEffect(() => { + if (modifiers.focused) ref.current?.focus(); + }, [modifiers.focused]); + + return ( + - + ); diff --git a/packages/common/components/src/ui/date-range-picker/index.tsx b/packages/common/components/src/ui/date-range-picker/index.tsx index 3572ffb7b..1e8cdfe4c 100644 --- a/packages/common/components/src/ui/date-range-picker/index.tsx +++ b/packages/common/components/src/ui/date-range-picker/index.tsx @@ -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 & { - value?: DateRange; - defaultValue?: DateRange; - disabled?: boolean; - onValueChange?: (date: DateRange | undefined) => void; -}; +type DateRangePickerProps = React.HTMLAttributes & + React.ComponentProps & { + 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( defaultValue, @@ -77,7 +79,7 @@ export function DateRangePicker({
{children} =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: From b8190c3550ad284d943d28d3a06b06b94198774f Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 27 Jun 2025 18:23:38 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=ED=8F=89=EA=B0=80=20=EB=8C=93=EA=B8=80=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/model/comment-interface.ts | 2 +- apps/cs/src/pages/bbs/ui/bbs-form.tsx | 6 +- apps/cs/src/widgets/post/ui/post-comment.tsx | 184 ++++++++++++------ 3 files changed, 127 insertions(+), 65 deletions(-) diff --git a/apps/cs/src/entities/comment/model/comment-interface.ts b/apps/cs/src/entities/comment/model/comment-interface.ts index 9023346af..d47b0c195 100644 --- a/apps/cs/src/entities/comment/model/comment-interface.ts +++ b/apps/cs/src/entities/comment/model/comment-interface.ts @@ -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({ diff --git a/apps/cs/src/pages/bbs/ui/bbs-form.tsx b/apps/cs/src/pages/bbs/ui/bbs-form.tsx index b472073db..f0b1d7ea2 100644 --- a/apps/cs/src/pages/bbs/ui/bbs-form.tsx +++ b/apps/cs/src/pages/bbs/ui/bbs-form.tsx @@ -311,7 +311,11 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) { >
- + +
+
+ +
diff --git a/apps/cs/src/widgets/post/ui/post-comment.tsx b/apps/cs/src/widgets/post/ui/post-comment.tsx index 53ad247f3..0e96ae1cb 100644 --- a/apps/cs/src/widgets/post/ui/post-comment.tsx +++ b/apps/cs/src/widgets/post/ui/post-comment.tsx @@ -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(null); - const [editValue, setEditValue] = useState(""); const { data: comments } = useQuery({ queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS, pstNo], @@ -123,6 +132,46 @@ function PostComment({ bbsId, pstNo }: { bbsId: number; pstNo: number }) {
+ {bbsInfo?.cmntUseYn === "C" && ( + ( + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ )} + /> + )} -
-
- - {comment.wrtrNm} - - - {dayjs(comment.regDt).format("YYYY.MM.DD")} - -
- {editingCmntNo === comment.cmntNo ? ( -
-