diff --git a/src/components/layouts/BaseLayout/componets/Navbar/Navbar.tsx b/src/components/layouts/BaseLayout/componets/Navbar/Navbar.tsx deleted file mode 100644 index 8f0d5f9..0000000 --- a/src/components/layouts/BaseLayout/componets/Navbar/Navbar.tsx +++ /dev/null @@ -1,76 +0,0 @@ -"use client"; - -import React, { useCallback } from "react"; -import { usePathname } from "next/navigation"; -import Link from "next/link"; -import { Menus, PATH } from "@/constant/config"; -import clsx from "clsx"; -import { Hospital } from "lucide-react"; - -const Navbar = () => { - const pathname = usePathname(); - - const checkActive = useCallback( - (pathActive: string) => { - // Handle root path "/" as dashboard - if (pathname === "/" && pathActive === "/dashboard") { - return true; - } - - const currentRoute = pathname.split("/")[1]; - return pathActive === `/${currentRoute}`; - }, - [pathname], - ); - - return ( -
- - {/* Logo */} - -

- Quản lý khám bệnh -

- -
- {Menus.map((menu, i) => ( -
- {/* TITLE */} -
- {menu.title} -
- -
- {menu.group.map((tab, y) => { - const isActive = checkActive(tab.pathActive); - - return ( - - -

{tab.title}

- - ); - })} -
-
- ))} -
-
- ); -}; - -export default Navbar; diff --git a/src/components/layouts/BaseLayout/componets/Navbar/index.ts b/src/components/layouts/BaseLayout/componets/Navbar/index.ts deleted file mode 100644 index 8d95d66..0000000 --- a/src/components/layouts/BaseLayout/componets/Navbar/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./Navbar"; diff --git a/src/components/utils/FilterDateRage/FilterDateRange.tsx b/src/components/utils/FilterDateRage/FilterDateRange.tsx deleted file mode 100644 index 9e52a04..0000000 --- a/src/components/utils/FilterDateRage/FilterDateRange.tsx +++ /dev/null @@ -1,85 +0,0 @@ -"use client"; - -import React, { useEffect, useState } from "react"; -import clsx from "clsx"; -import * as Popover from "@radix-ui/react-popover"; - -import { PropsFilterDateRange } from "./interface"; -import Moment from "react-moment"; -import { TYPE_DATE } from "@/constant/config/enum"; -import { getDateRange } from "@/common/funcs/selectData"; -import { ListOptionFilterDate } from "@/constant/config"; -import { ChevronDown } from "lucide-react"; -import DateOption from "./components/DateOption"; - -function FilterDateRange({ - styleRounded = false, - showOptionAll = false, - date, - setDate, - typeDate, - setTypeDate, - title = "Thời gian", -}: PropsFilterDateRange) { - const [openDate, setOpenDate] = useState(false); - - useEffect(() => { - if (typeDate !== null && typeDate !== TYPE_DATE.LUA_CHON) { - setDate(getDateRange(typeDate)); - } - }, [typeDate, setDate]); - - return ( - - - - - - - - - - ); -} - -export default FilterDateRange; diff --git a/src/components/utils/FilterDateRage/components/CalendarMain/CalendarMain.tsx b/src/components/utils/FilterDateRage/components/CalendarMain/CalendarMain.tsx deleted file mode 100644 index be006da..0000000 --- a/src/components/utils/FilterDateRage/components/CalendarMain/CalendarMain.tsx +++ /dev/null @@ -1,230 +0,0 @@ -"use client"; - -import { memo, useCallback, useContext, useMemo } from "react"; -import clsx from "clsx"; -import { ContextCalendar } from "../RangeDatePicker/RangeDatePicker"; -import { PropsCalendarMain } from "./interface"; - -const months = Array.from({ length: 12 }, (_, i) => i + 1); - -function CalendarMain({ - date, - setDate, - setType, - type, - year, -}: PropsCalendarMain) { - // ✅ destructure context (fix React Compiler) - const { datePicker, dateHover, setDatePicker, setDateHover } = - useContext(ContextCalendar); - - // ===================== HOVER RANGE ===================== - const hoverRange = useMemo(() => { - if (datePicker?.from && dateHover) { - return dateHover > datePicker.from - ? { start: datePicker.from, end: dateHover } - : { start: dateHover, end: datePicker.from }; - } - return null; - }, [datePicker?.from, dateHover]); - - // ===================== HANDLE PICK ===================== - const handleDatePick = useCallback( - (datePick: Date) => { - if (!datePicker?.from || datePicker?.to) { - return setDatePicker({ - from: datePick, - to: null, - }); - } - - setDateHover(null); - - if (datePick < datePicker.from) { - return setDatePicker({ - from: datePick, - to: datePicker.from, - }); - } - - return setDatePicker({ - ...datePicker, - to: datePick, - }); - }, - [datePicker, setDatePicker, setDateHover], - ); - - // ===================== HANDLE HOVER ===================== - const handleHover = useCallback( - (d: Date) => { - if (datePicker?.from && !datePicker?.to) { - setDateHover(d); - } - }, - [datePicker?.from, datePicker?.to, setDateHover], - ); - - // ===================== BUILD CALENDAR ===================== - const rows = useMemo(() => { - const currentDate = new Date(); - const monthStart = new Date(date.getFullYear(), date.getMonth(), 1); - const monthEnd = new Date(date.getFullYear(), date.getMonth() + 1, 0); - - const startDate = new Date(monthStart); - const result: React.ReactNode[] = []; - - // lùi về CN - while (startDate.getDay() !== 0) { - startDate.setDate(startDate.getDate() - 1); - } - - while (startDate <= monthEnd) { - const cells: React.ReactNode[] = []; - - for (let i = 0; i < 7; i++) { - const currDate = new Date(startDate); - - const isStart = currDate.getTime() === datePicker?.from?.getTime(); - const isEnd = currDate.getTime() === datePicker?.to?.getTime(); - - const isBetween = - datePicker?.from && - datePicker?.to && - currDate > datePicker.from && - currDate < datePicker.to; - - const isHover = - hoverRange && - currDate > hoverRange.start && - currDate < hoverRange.end; - - const isStartHover = - hoverRange && currDate.getTime() === hoverRange.start.getTime(); - - const isEndHover = - hoverRange && currDate.getTime() === hoverRange.end.getTime(); - - const isDisabled = startDate.getMonth() !== date.getMonth(); - - const isToday = - startDate.toDateString() === currentDate.toDateString() && - startDate.getMonth() === date.getMonth(); - - cells.push( -
handleDatePick(currDate)} - onMouseOver={() => handleHover(currDate)} - className={clsx( - "flex items-center justify-center px-2 py-1 text-sm font-medium rounded border border-transparent transition", - - "cursor-pointer select-none hover:opacity-60", - - isDisabled && "pointer-events-none opacity-30", - - isToday && "border-blue-700", - - isStart && "bg-blue-700 text-white rounded-l-md", - isEnd && "bg-blue-700 text-white rounded-r-md", - - isBetween && "bg-gray-100", - - isHover && "border-y border-blue-700", - isStartHover && "border border-blue-700 rounded-l-md", - isEndHover && "border border-blue-700 rounded-r-md", - )} - > - {startDate.getDate()} -
, - ); - - startDate.setDate(startDate.getDate() + 1); - } - - result.push( -
- {cells} -
, - ); - } - - return result; - }, [ - date, - datePicker?.from, - datePicker?.to, - handleDatePick, - handleHover, - hoverRange, - ]); - - // ===================== YEAR LIST ===================== - const listYear = useMemo(() => { - if (!year) return []; - return Array.from( - { length: year.last - year.first + 1 }, - (_, i) => year.first + i, - ); - }, [year]); - - // ===================== UI ===================== - return ( -
setDateHover(null)}> - {/* DATE */} - {type === 0 && rows} - - {/* MONTH */} - {type === 1 && ( -
- {months.map((m) => ( -
{ - const newDate = new Date(date); - newDate.setMonth(m - 1); - setDate(newDate); - setType(0); - }} - className={clsx( - "cursor-pointer select-none rounded px-2 py-1 text-center text-sm font-semibold transition", - "hover:bg-gray-100 hover:opacity-70", - m === date.getMonth() + 1 && - "bg-blue-500 text-white hover:bg-blue-500 hover:opacity-100", - )} - > - Tháng {m} -
- ))} -
- )} - - {/* YEAR */} - {type === 2 && ( -
- {listYear.map((y) => ( -
{ - const newDate = new Date(date); - newDate.setFullYear(y); - setDate(newDate); - setType(1); - }} - className={clsx( - "cursor-pointer select-none rounded px-2 py-1 text-center text-sm font-semibold transition", - "hover:bg-gray-100 hover:opacity-70", - y === date.getFullYear() && - "bg-blue-500 text-white hover:bg-blue-500 hover:opacity-100", - )} - > - {y} -
- ))} -
- )} -
- ); -} - -export default memo(CalendarMain); diff --git a/src/components/utils/FilterDateRage/components/CalendarMain/index.ts b/src/components/utils/FilterDateRage/components/CalendarMain/index.ts deleted file mode 100644 index cadb07e..0000000 --- a/src/components/utils/FilterDateRage/components/CalendarMain/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./CalendarMain"; diff --git a/src/components/utils/FilterDateRage/components/CalendarMain/interface/index.ts b/src/components/utils/FilterDateRage/components/CalendarMain/interface/index.ts deleted file mode 100644 index 0a74bfd..0000000 --- a/src/components/utils/FilterDateRage/components/CalendarMain/interface/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface PropsCalendarMain { - date: Date; - setDate: (date: Date) => void; - setType: (num: number) => void; - type: number; - year: any; -} diff --git a/src/components/utils/FilterDateRage/components/DateOption/DateOption.tsx b/src/components/utils/FilterDateRage/components/DateOption/DateOption.tsx deleted file mode 100644 index 3f37f0e..0000000 --- a/src/components/utils/FilterDateRage/components/DateOption/DateOption.tsx +++ /dev/null @@ -1,77 +0,0 @@ -"use client"; - -import clsx from "clsx"; -import { Check } from "lucide-react"; - -import { PropsDateOption } from "./interface"; - -import { TYPE_DATE } from "@/constant/config/enum"; -import { getDateRange } from "@/common/funcs/selectData"; -import { ListOptionFilterDate } from "@/constant/config"; - -import RangeDatePicker from "../RangeDatePicker"; - -export default function DateOption({ - showOptionAll, - date, - setDate, - typeDate, - setTypeDate, - show, - setShow, -}: PropsDateOption) { - const isCustom = Number(typeDate) === TYPE_DATE.LUA_CHON; - - return ( -
- {/* MENU */} -
- {ListOptionFilterDate.map((item) => { - const active = item.value === typeDate; - - return ( -
{ - setTypeDate(item.value); - - if (item.value !== TYPE_DATE.LUA_CHON) { - setDate(getDateRange(item.value)); - setShow(false); - } - }} - className={clsx( - "flex cursor-pointer items-center justify-between rounded-lg px-3 py-2 text-sm", - active ? "bg-blue-50 text-blue-600" : "hover:bg-gray-50", - )} - > - {item.name} - - {active && item.value !== TYPE_DATE.LUA_CHON && ( - - )} -
- ); - })} -
- - {/* DATE PICKER */} - {isCustom && ( - setShow(false)} - /> - )} -
- ); -} diff --git a/src/components/utils/FilterDateRage/components/DateOption/index.ts b/src/components/utils/FilterDateRage/components/DateOption/index.ts deleted file mode 100644 index 09e27e5..0000000 --- a/src/components/utils/FilterDateRage/components/DateOption/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./DateOption"; diff --git a/src/components/utils/FilterDateRage/components/DateOption/interface/index.ts b/src/components/utils/FilterDateRage/components/DateOption/interface/index.ts deleted file mode 100644 index 5a52221..0000000 --- a/src/components/utils/FilterDateRage/components/DateOption/interface/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -export interface PropsDateOption { - showOptionAll?: boolean; - date: { - from: Date | null; - to: Date | null; - } | null; - setDate: (any: any) => void; - typeDate: number | null; - setTypeDate: (any: any) => void; - show: boolean; - setShow: (any: any) => void; -} diff --git a/src/components/utils/FilterDateRage/components/RangeDatePicker/RangeDatePicker.tsx b/src/components/utils/FilterDateRage/components/RangeDatePicker/RangeDatePicker.tsx deleted file mode 100644 index 4021770..0000000 --- a/src/components/utils/FilterDateRage/components/RangeDatePicker/RangeDatePicker.tsx +++ /dev/null @@ -1,220 +0,0 @@ -"use client"; - -import { createContext, memo, useCallback, useEffect, useState } from "react"; -import { PropsRangeDatePicker } from "./interface"; -import clsx from "clsx"; -import { ChevronLeft, MoveRight, ChevronRight } from "lucide-react"; -import CalendarMain from "../CalendarMain"; -import Button from "@/components/customs/custom-button"; -import Moment from "react-moment"; -import CustomButton from "@/components/customs/custom-button"; - -const daysOfWeek = ["CN", "T2", "T3", "T4", "T5", "T6", "T7"]; - -export const ContextCalendar = createContext(null); - -function RangeDatePicker({ - onClose, - onSetValue, - value, - open, - onSubmit, -}: PropsRangeDatePicker) { - const [typeCalendarLeft, setTypeCalendarLeft] = useState(0); - const [typeCalendarRight, setTypeCalendarRight] = useState(0); - const [dateLeft, setDateLeft] = useState(new Date()); - const [dateRight, setDateRight] = useState( - new Date(new Date().getFullYear(), new Date().getMonth() + 1), - ); - - const [dateHover, setDateHover] = useState(null); - const [yearTableLeft, setYearTableLeft] = useState(); - const [yearTableRight, setYearTableRight] = useState(); - const [datePicker, setDatePicker] = useState({ - from: null as Date | null, - to: null as Date | null, - }); - - // ===== HANDLE ===== - const handleNext = useCallback( - (isRight?: boolean) => { - if (isRight) { - if (typeCalendarRight === 0) { - setDateRight((prev) => { - const newDate = new Date(prev.getFullYear(), prev.getMonth() + 1); - return newDate; - }); - } - } else { - if (typeCalendarLeft === 0) { - setDateLeft((prev) => { - const newDate = new Date(prev.getFullYear(), prev.getMonth() + 1); - return newDate < dateRight ? newDate : prev; - }); - } - } - }, - [dateRight, typeCalendarLeft, typeCalendarRight], - ); - - const handlePrev = useCallback( - (isRight?: boolean) => { - if (isRight) { - if (typeCalendarRight === 0) { - setDateRight((prev) => { - const newDate = new Date(prev.getFullYear(), prev.getMonth() - 1); - return dateLeft < newDate ? newDate : prev; - }); - } - } else { - if (typeCalendarLeft === 0) { - setDateLeft((prev) => { - return new Date(prev.getFullYear(), prev.getMonth() - 1); - }); - } - } - }, - [dateLeft, typeCalendarLeft, typeCalendarRight], - ); - - const handleSubmit = useCallback(() => { - if (datePicker.from && datePicker.to) { - onSetValue(datePicker); - onClose(); - onSubmit && onSubmit(); - } - }, [datePicker]); - - // ===== EFFECT ===== - useEffect(() => { - if (!open) { - setDatePicker({ from: value?.from, to: value?.to }); - } - }, [open, value]); - - useEffect(() => { - setYearTableLeft({ - first: dateLeft.getFullYear() - 5, - last: dateLeft.getFullYear() + 6, - }); - setYearTableRight({ - first: dateRight.getFullYear() - 5, - last: dateRight.getFullYear() + 6, - }); - }, [dateLeft, dateRight]); - - return ( - -
- {/* HEADER DATE */} -
- - {datePicker.from ? ( - {datePicker.from} - ) : ( - "Ngày bắt đầu" - )} - - - - - - {datePicker.to ? ( - {datePicker.to} - ) : ( - "Ngày kết thúc" - )} - -
- - {/* CALENDAR */} -
- {[false, true].map((isRight, idx) => { - const date = isRight ? dateRight : dateLeft; - const type = isRight ? typeCalendarRight : typeCalendarLeft; - const setType = isRight - ? setTypeCalendarRight - : setTypeCalendarLeft; - const year = isRight ? yearTableRight : yearTableLeft; - - return ( -
- {/* TITLE */} -
- - -

setType(type === 0 ? 1 : type === 1 ? 2 : 0)} - > - Tháng {date.getMonth() + 1}, {date.getFullYear()} -

- - -
- - {/* DAYS */} - {type === 0 && ( -
- {daysOfWeek.map((d) => ( -
- {d} -
- ))} -
- )} - - {/* CALENDAR MAIN */} - -
- ); - })} -
- - {/* BUTTON */} -
- Hủy bỏ - - Áp dụng - -
-
-
- ); -} - -export default memo(RangeDatePicker); diff --git a/src/components/utils/FilterDateRage/components/RangeDatePicker/index.ts b/src/components/utils/FilterDateRage/components/RangeDatePicker/index.ts deleted file mode 100644 index 68339b9..0000000 --- a/src/components/utils/FilterDateRage/components/RangeDatePicker/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./RangeDatePicker"; diff --git a/src/components/utils/FilterDateRage/components/RangeDatePicker/interface/index.ts b/src/components/utils/FilterDateRage/components/RangeDatePicker/interface/index.ts deleted file mode 100644 index 1a4febd..0000000 --- a/src/components/utils/FilterDateRage/components/RangeDatePicker/interface/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface PropsRangeDatePicker { - onClose: () => void; - onSetValue: (any: any) => void; - onSubmit?: () => void; - value: any; - open?: boolean; -} diff --git a/src/components/utils/FilterDateRage/index.ts b/src/components/utils/FilterDateRage/index.ts deleted file mode 100644 index 18d4824..0000000 --- a/src/components/utils/FilterDateRage/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./FilterDateRange"; diff --git a/src/components/utils/FilterDateRage/interface/index.ts b/src/components/utils/FilterDateRage/interface/index.ts deleted file mode 100644 index 5e6d459..0000000 --- a/src/components/utils/FilterDateRage/interface/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Dispatch, SetStateAction } from "react"; -import { TYPE_DATE } from "@/constant/config/enum"; - -export interface PropsFilterDateRange { - styleRounded?: boolean; - showOptionAll?: boolean; - date: { - from: Date | null; - to: Date | null; - } | null; - setDate: Dispatch< - SetStateAction<{ from: Date | null; to: Date | null } | null> - >; - typeDate: TYPE_DATE; - setTypeDate: Dispatch>; - title?: string; -} diff --git a/src/components/utils/PositionContainer/PositionContainer.tsx b/src/components/utils/PositionContainer/PositionContainer.tsx deleted file mode 100644 index 34ce4d5..0000000 --- a/src/components/utils/PositionContainer/PositionContainer.tsx +++ /dev/null @@ -1,116 +0,0 @@ -"use client"; - -import { ReactNode, useEffect, useRef, useState } from "react"; - -import { createPortal } from "react-dom"; -import clsx from "clsx"; - -import { PropsPositionContainer } from "./interface"; - -export default function PositionContainer({ - children, - open, - onClose, - disableOverlay, - idParent, - classStyle, -}: PropsPositionContainer) { - const containerRef = useRef(null); - - // ✅ create portal element ONCE - const [portalElement] = useState(() => { - if (typeof window === "undefined") { - return null; - } - - return document.createElement("div"); - }); - - // ================= APPEND PORTAL ================= - useEffect(() => { - if (!portalElement) return; - - const parent = idParent ? document.getElementById(idParent) : document.body; - - if (!parent) return; - - parent.appendChild(portalElement); - - return () => { - parent.removeChild(portalElement); - }; - }, [idParent, portalElement]); - - // ================= CLICK OUTSIDE ================= - useEffect(() => { - if (!disableOverlay || !open) return; - - const handleMouseUp = (e: MouseEvent) => { - const target = e.target as HTMLElement; - - const insideClick = target.closest(".click"); - - if ( - containerRef.current && - !containerRef.current.contains(target) && - !insideClick - ) { - onClose(); - } - }; - - document.addEventListener("mouseup", handleMouseUp); - - return () => { - document.removeEventListener("mouseup", handleMouseUp); - }; - }, [disableOverlay, open, onClose]); - - // ================= SSR SAFE ================= - if (!portalElement) return null; - - return createPortal( -
- {/* overlay */} - {!disableOverlay && ( -
- )} - - {/* panel */} -
- {children} -
-
, - portalElement, - ); -} diff --git a/src/components/utils/PositionContainer/components/ClickOpen/ClickOpen.tsx b/src/components/utils/PositionContainer/components/ClickOpen/ClickOpen.tsx deleted file mode 100644 index 3528f9e..0000000 --- a/src/components/utils/PositionContainer/components/ClickOpen/ClickOpen.tsx +++ /dev/null @@ -1,36 +0,0 @@ -"use client"; - -import { useEffect, useRef } from "react"; -import { PropsClickOpen } from "./interface"; - -export default function ClickOpen({ - children, - onClick, - duration = 100, -}: PropsClickOpen) { - const timeoutRef = useRef(null); - - const handleClick = () => { - // clear timeout cũ nếu spam click - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - - onClick(false); - - timeoutRef.current = setTimeout(() => { - onClick(true); - }, duration); - }; - - useEffect(() => { - return () => { - // cleanup khi unmount - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - }; - }, []); - - return
{children}
; -} diff --git a/src/components/utils/PositionContainer/components/ClickOpen/index.ts b/src/components/utils/PositionContainer/components/ClickOpen/index.ts deleted file mode 100644 index 19f3c55..0000000 --- a/src/components/utils/PositionContainer/components/ClickOpen/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./ClickOpen"; diff --git a/src/components/utils/PositionContainer/components/ClickOpen/interface/index.ts b/src/components/utils/PositionContainer/components/ClickOpen/interface/index.ts deleted file mode 100644 index 5f77f1b..0000000 --- a/src/components/utils/PositionContainer/components/ClickOpen/interface/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface PropsClickOpen { - children: any; - onClick: (bol: boolean) => void; - duration?: number; -} diff --git a/src/components/utils/PositionContainer/index.ts b/src/components/utils/PositionContainer/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/utils/PositionContainer/interface/index.ts b/src/components/utils/PositionContainer/interface/index.ts deleted file mode 100644 index d0dec60..0000000 --- a/src/components/utils/PositionContainer/interface/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface PropsPositionContainer { - children: any; - open: boolean; - onClose: () => void; - disableOverlay?: boolean; - idParent?: string; - classStyle?: { - main: string; - open: string; - }; -} diff --git a/src/components/utils/UploadAvatar/UploadAvatar.tsx b/src/components/utils/UploadAvatar/UploadAvatar.tsx deleted file mode 100644 index a09bde5..0000000 --- a/src/components/utils/UploadAvatar/UploadAvatar.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import React, { useEffect, useState } from "react"; - -import { PropsUploadAvatar } from "./interface"; -// import { toastError, toastWarn } from "~/common/funcs/toast"; -import Image from "next/image"; -import { toastError, toastWarn } from "@/common/funcs/toast"; -import { Trash } from "lucide-react"; -// import icons from "~/constants/images/icons"; - -const MAXIMUM_FILE = 10; - -function UploadAvatar({ path, name, onSetFile, resetPath }: PropsUploadAvatar) { - const [imageBase64, setImageBase64] = useState(""); - const [fileName, setFileName] = useState(""); - - const handleSelectImg = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - - if (file) { - const { size, type, name } = file; - const maxSize = MAXIMUM_FILE; // MB - - if (size / 1000000 > maxSize) { - return toastError({ - msg: `Kích thước tối đa của ảnh là ${maxSize} MB`, - }); - } else if (!["image/jpeg", "image/jpg", "image/png"].includes(type)) { - return toastWarn({ - msg: `Định dạng tệp không chính xác, chỉ chấp nhận .jpg, .jpeg, .png`, - }); - } - - const imageUrl = URL.createObjectURL(file); - setImageBase64((prev) => { - URL.revokeObjectURL(prev); - return imageUrl; - }); - setFileName(name); - onSetFile && onSetFile(file); - } - }; - - useEffect(() => { - return () => { - if (imageBase64) { - URL.revokeObjectURL(imageBase64); - } - }; - }, [imageBase64]); - - const handleRemoveImg = () => { - setImageBase64(""); - setFileName(""); - onSetFile && onSetFile(null); - resetPath && resetPath(); - }; - - return ( -
- update avatar - -
-
- - - {/* Xóa avatar */} -
- -

- Xóa ảnh đại diện -

-
-
- -

- Hình ảnh được dùng làm ảnh đại diện, kích thước tối thiểu 300px X - 300px để đảm bảo độ sắc nét. -

-
-
- ); -} - -export default UploadAvatar; diff --git a/src/components/utils/UploadAvatar/index.ts b/src/components/utils/UploadAvatar/index.ts deleted file mode 100644 index 9c7d933..0000000 --- a/src/components/utils/UploadAvatar/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./UploadAvatar"; diff --git a/src/components/utils/UploadAvatar/interface/index.ts b/src/components/utils/UploadAvatar/interface/index.ts deleted file mode 100644 index 34cacd7..0000000 --- a/src/components/utils/UploadAvatar/interface/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Dispatch, SetStateAction } from "react"; - -export interface PropsUploadAvatar { - path: any; - name: string; - onSetFile: Dispatch>; - resetPath?: () => void; -} diff --git a/src/components/utils/UploadImage/UploadImage.tsx b/src/components/utils/UploadImage/UploadImage.tsx deleted file mode 100644 index fb04efb..0000000 --- a/src/components/utils/UploadImage/UploadImage.tsx +++ /dev/null @@ -1,243 +0,0 @@ -"use client"; - -import React, { useEffect, useMemo, useState } from "react"; - -import Image from "next/image"; - -import clsx from "clsx"; - -import { UploadCloud, X } from "lucide-react"; - -import { toastError, toastWarn } from "@/common/funcs/toast"; - -import { UploadImageProps } from "./interface"; - -const MAXIMUM_FILE = 10; // MB - -const ACCEPT_TYPES = ["image/jpeg", "image/jpg", "image/png"]; - -/* ========================= - COMPONENT -========================= */ - -const UploadImage = ({ - label, - name, - path, - file, - setFile, - resetPath, - isWidthFull = true, - disabled = false, -}: UploadImageProps) => { - const [dragging, setDragging] = useState(false); - - /* ========================= - PREVIEW IMAGE - ========================= */ - - const imagePreview = useMemo(() => { - if (!file) return ""; - - return URL.createObjectURL(file); - }, [file]); - - /* ========================= - CLEANUP OBJECT URL - ========================= */ - - useEffect(() => { - return () => { - if (imagePreview) { - URL.revokeObjectURL(imagePreview); - } - }; - }, [imagePreview]); - - /* ========================= - VALIDATE FILE - ========================= */ - - const validateFile = (selectedFile: File | null | undefined) => { - if (!selectedFile) return; - - const { size, type } = selectedFile; - - /** - * CHECK SIZE - */ - if (size / 1000000 > MAXIMUM_FILE) { - return toastError({ - msg: `Kích thước tối đa của ảnh là ${MAXIMUM_FILE} MB`, - }); - } - - /** - * CHECK TYPE - */ - if (!ACCEPT_TYPES.includes(type)) { - return toastWarn({ - msg: "Định dạng không hợp lệ. Chỉ chấp nhận JPG, JPEG, PNG", - }); - } - - /** - * SET FILE - */ - setFile(selectedFile); - }; - - /* ========================= - DRAG EVENTS - ========================= */ - - const handleDragEnter = (e: React.DragEvent): void => { - e.preventDefault(); - - if (disabled) return; - - setDragging(true); - }; - - const handleDragLeave = (): void => { - setDragging(false); - }; - - const handleDrop = (e: React.DragEvent): void => { - e.preventDefault(); - - if (disabled) return; - - setDragging(false); - - const droppedFile = e.dataTransfer.files?.[0]; - - validateFile(droppedFile); - }; - - /* ========================= - SELECT FILE - ========================= */ - - const handleSelectImage = (e: React.ChangeEvent): void => { - if (disabled) return; - - const selectedFile = e.target.files?.[0]; - - validateFile(selectedFile); - }; - - /* ========================= - REMOVE IMAGE - ========================= */ - - const handleRemoveImage = (): void => { - setFile(null); - - resetPath?.(); - }; - - /* ========================= - IMAGE SOURCE - ========================= */ - - const imageSrc = imagePreview || path || ""; - - /* ========================= - UI - ========================= */ - - return ( -
- {/* LABEL */} - {label && ( -
- {typeof label === "string" ? ( -

{label}

- ) : ( - label - )} -
- )} - - {/* PREVIEW */} - {imageSrc ? ( -
- Preview image - - {/* REMOVE BUTTON */} - {!disabled && ( - - )} -
- ) : ( - - )} -
- ); -}; - -export default UploadImage; diff --git a/src/components/utils/UploadImage/index.ts b/src/components/utils/UploadImage/index.ts deleted file mode 100644 index 87abefe..0000000 --- a/src/components/utils/UploadImage/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./UploadImage"; diff --git a/src/components/utils/UploadImage/interface/index.ts b/src/components/utils/UploadImage/interface/index.ts deleted file mode 100644 index c44672b..0000000 --- a/src/components/utils/UploadImage/interface/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -export interface UploadImageProps { - isWidthFull?: boolean; - - label?: string | React.ReactNode; - - name: string; - - file: File | null; - - setFile: (file: File | null) => void; - - path?: string; - - resetPath?: () => void; - - disabled?: boolean; -} diff --git a/src/components/utils/UploadMultipleFile/UploadMultipleFile.tsx b/src/components/utils/UploadMultipleFile/UploadMultipleFile.tsx deleted file mode 100644 index e89c3ba..0000000 --- a/src/components/utils/UploadMultipleFile/UploadMultipleFile.tsx +++ /dev/null @@ -1,211 +0,0 @@ -"use client"; - -import React from "react"; -import Image from "next/image"; -import { - X, - CirclePlus, - FileText, - FileSpreadsheet, - FileType, - FileImage, -} from "lucide-react"; - -import clsx from "clsx"; -import { PropsUploadMultipleFile, UploadFileItem } from "./interface"; - -export default function UploadMultipleFile({ - images = [], - setImages, - isDisableDelete = false, -}: PropsUploadMultipleFile) { - /* ========================= - CHECK IMAGE FILE - ========================= */ - const isImageFile = (item: UploadFileItem) => { - const type = item?.fileType || ""; - - return ( - type.startsWith("image/") || - /\.(jpg|jpeg|png|gif|webp)$/i.test(item?.path || "") - ); - }; - - /* ========================= - GET FILE ICON - ========================= */ - const renderFileIcon = (item: UploadFileItem) => { - const fileName = item?.fileName || item?.path?.split("/").pop() || ""; - - const ext = fileName.split(".").pop()?.toLowerCase(); - - switch (ext) { - case "jpg": - case "jpeg": - case "png": - case "gif": - case "webp": - return ; - - case "pdf": - return ; - - case "xls": - case "xlsx": - return ; - - case "doc": - case "docx": - return ; - - case "ppt": - case "pptx": - return ; - - case "txt": - return ; - - default: - return ; - } - }; - - /* ========================= - UPLOAD FILES - ========================= */ - const handleFileChange = (event: React.ChangeEvent) => { - const files = event.target.files; - - if (!files) return; - - const newFiles: UploadFileItem[] = Array.from(files).map((file) => ({ - file, - url: URL.createObjectURL(file), - fileName: file.name, - fileType: file.type, - })); - - setImages((prev) => [...prev, ...newFiles]); - }; - - /* ========================= - DELETE FILE - ========================= */ - const handleDelete = (index: number) => { - setImages((prev) => { - const target = prev[index]; - - if (target?.url) { - URL.revokeObjectURL(target.url); - } - - return prev.filter((_, i) => i !== index); - }); - }; - - return ( -
- {/* FILE LIST */} - {images?.length > 0 && ( -
- {images.map((item, index) => { - const fileUrl = - item?.url || - (item?.path - ? `${process.env.NEXT_PUBLIC_IMAGE}${item.path}` - : ""); - - return ( -
- {/* IMAGE */} - {isImageFile(item) ? ( - - file - - ) : ( - - {renderFileIcon(item)} - - - {item?.fileName || item?.path?.split("/").pop()} - - - )} - - {/* DELETE */} - {isDisableDelete && !item?.file && item?.img ? null : ( - - )} -
- ); - })} -
- )} - - {/* UPLOAD BUTTON */} -
- - -
-

Upload file

- -

- JPG, PNG, PDF, Word, Excel, PPT (≤ 50MB) -

-
-
-
- ); -} diff --git a/src/components/utils/UploadMultipleFile/index.ts b/src/components/utils/UploadMultipleFile/index.ts deleted file mode 100644 index 7f24387..0000000 --- a/src/components/utils/UploadMultipleFile/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./UploadMultipleFile"; diff --git a/src/components/utils/UploadMultipleFile/interface/index.ts b/src/components/utils/UploadMultipleFile/interface/index.ts deleted file mode 100644 index e44b849..0000000 --- a/src/components/utils/UploadMultipleFile/interface/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface UploadFileItem { - file?: File | null; - url?: string; - path?: string; - img?: string; - - fileName?: string; - fileType?: string; -} - -export interface PropsUploadMultipleFile { - images: UploadFileItem[]; - setImages: React.Dispatch>; - isDisableDelete?: boolean; -} diff --git a/src/constant/config/index.ts b/src/constant/config/index.ts index fb4ed8d..d07b8f3 100644 --- a/src/constant/config/index.ts +++ b/src/constant/config/index.ts @@ -1,22 +1,14 @@ -import { - ChartNoAxesCombined, - CircleGauge, - HeartPulse, - Pill, - Users, -} from "lucide-react"; +// import { +// ChartNoAxesCombined, +// CircleGauge, +// HeartPulse, +// Pill, +// Users, +// } from "lucide-react"; import { TYPE_DATE } from "./enum"; export enum PATH { HOME = "/", - DASHBOARD = "/dashboard", - PATIENT = "/patient", - CONSULTATION = "/consultation", - PRESCRIPTION = "/prescription", - CREATE_PRESCRIPTION = "/prescription/create", - - STATISTICAL = "/statistical", - LOGIN = "/auth/login", REGISTER = "/auth/register", FORGOT_PASSWORD = "/auth/forgot-password", @@ -25,51 +17,51 @@ export enum PATH { UPDATEPROFILE = "/profile/update", } -export const Menus: { - title?: string; - group: { - path: string; - pathActive: string; - // isSpecial?: TYPE_SPECIAL; - title: string; - icon: React.ElementType; - }[]; -}[] = [ - { - group: [ - { - title: "Dashboard", - icon: CircleGauge, - path: PATH.DASHBOARD || PATH.HOME, - pathActive: PATH.DASHBOARD, - }, - { - title: "Bệnh nhân", - icon: Users, - path: PATH.PATIENT, - pathActive: PATH.PATIENT, - }, - { - title: "Phiên khám", - icon: HeartPulse, - path: PATH.CONSULTATION, - pathActive: PATH.CONSULTATION, - }, - { - title: "Đơn thuốc", - icon: Pill, - path: PATH.PRESCRIPTION, - pathActive: PATH.PRESCRIPTION, - }, - { - title: "Thống kê chuyên khoa", - icon: ChartNoAxesCombined, - path: PATH.STATISTICAL, - pathActive: PATH.STATISTICAL, - }, - ], - }, -]; +// export const Menus: { +// title?: string; +// group: { +// path: string; +// pathActive: string; +// // isSpecial?: TYPE_SPECIAL; +// title: string; +// icon: React.ElementType; +// }[]; +// }[] = [ +// { +// group: [ +// { +// title: "Dashboard", +// icon: CircleGauge, +// path: PATH.DASHBOARD || PATH.HOME, +// pathActive: PATH.DASHBOARD, +// }, +// { +// title: "Bệnh nhân", +// icon: Users, +// path: PATH.PATIENT, +// pathActive: PATH.PATIENT, +// }, +// { +// title: "Phiên khám", +// icon: HeartPulse, +// path: PATH.CONSULTATION, +// pathActive: PATH.CONSULTATION, +// }, +// { +// title: "Đơn thuốc", +// icon: Pill, +// path: PATH.PRESCRIPTION, +// pathActive: PATH.PRESCRIPTION, +// }, +// { +// title: "Thống kê chuyên khoa", +// icon: ChartNoAxesCombined, +// path: PATH.STATISTICAL, +// pathActive: PATH.STATISTICAL, +// }, +// ], +// }, +// ]; export const ListOptionFilterDate: { name: string;