diff --git a/frontend/app/product/books/[bookId]/page.tsx b/frontend/app/product/books/[bookId]/page.tsx index 2541987..205c766 100644 --- a/frontend/app/product/books/[bookId]/page.tsx +++ b/frontend/app/product/books/[bookId]/page.tsx @@ -15,7 +15,12 @@ import { Label } from "@/components/ui/label"; import { useToast } from "@/components/ui/use-toast"; import Link from "next/link"; import { useEffect, useState } from "react"; -import { SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form"; +import { + Controller, + SubmitErrorHandler, + SubmitHandler, + useForm, +} from "react-hook-form"; import { LuCheck } from "react-icons/lu"; import { zodResolver } from "@hookform/resolvers/zod"; import * as z from "zod"; @@ -38,6 +43,7 @@ import { BookTitle } from "@/types"; import BookTitleSelectEdit from "@/components/book-manage/book-title-select-edit"; import { useLoading } from "@/hooks/loading-context"; import BookDetailSkeleton from "@/components/skeleton/book-detail-skeleton"; +import { NumericFormat } from "react-number-format"; const FormSchema = z.object({ bookTitleId: z.string().min(1, "Vui lòng chọn một đầu sách"), @@ -194,7 +200,7 @@ const EditBook = ({ params }: { params: { bookId: string } }) => { const { currentUser } = useCurrentUser(); if (!currentUser || isLoading) { - return ; + return ; } else if ( currentUser && !includesRoles({ @@ -335,10 +341,26 @@ const EditBook = ({ params }: { params: { bookId: string } }) => {
- ( + { + const numericValue = parseFloat( + values.value.replace(/,/g, "") + ); + + field.onChange(numericValue); + }} + readOnly={readOnly} + thousandSeparator="." + decimalSeparator="," + valueIsNumericString + customInput={Input} + /> + )} /> {errors.listedPrice && ( @@ -348,10 +370,25 @@ const EditBook = ({ params }: { params: { bookId: string } }) => {
- ( + { + const numericValue = parseFloat( + values.value.replace(/,/g, "") + ); + field.onChange(numericValue); + }} + readOnly={readOnly} + thousandSeparator="." + decimalSeparator="," + valueIsNumericString + customInput={Input} + /> + )} /> {errors.sellPrice && ( diff --git a/frontend/app/product/books/add/page.tsx b/frontend/app/product/books/add/page.tsx index 8d7d0ff..6c68f7e 100644 --- a/frontend/app/product/books/add/page.tsx +++ b/frontend/app/product/books/add/page.tsx @@ -15,7 +15,12 @@ import { Label } from "@/components/ui/label"; import { useToast } from "@/components/ui/use-toast"; import Link from "next/link"; import { useState } from "react"; -import { SubmitHandler, useFieldArray, useForm } from "react-hook-form"; +import { + Controller, + SubmitHandler, + useFieldArray, + useForm, +} from "react-hook-form"; import { LuCheck } from "react-icons/lu"; import { zodResolver } from "@hookform/resolvers/zod"; import * as z from "zod"; @@ -32,6 +37,7 @@ import { includesRoles } from "@/lib/utils"; import NoRole from "@/components/no-role"; import { useLoading } from "@/hooks/loading-context"; import BookAddSkeleton from "@/components/skeleton/book-add-skeleton"; +import { NumericFormat } from "react-number-format"; const FormSchema = z.object({ bookTitleId: z.string().min(1, "Vui lòng chọn một đầu sách"), @@ -80,6 +86,7 @@ const InsertNewBook = () => { setValue, reset, trigger, + clearErrors, formState: { errors }, } = form; @@ -139,20 +146,23 @@ const InsertNewBook = () => { title: "Thành công", description: "Thêm mới sách thành công", }); + router.refresh(); + + setPublisherId(""); + reset({ bookTitleId: data.bookTitleId, idBook: "", edition: 1, publisherId: "", - listedPrice: 0, - sellPrice: 0, + listedPrice: 1, + sellPrice: 1, image: "/no-image.jpg", }); - setPublisherId(""); + clearErrors(); setImage(null); mutate(`${endPoint}/v1/books/all`); - router.refresh(); } }; @@ -264,7 +274,25 @@ const InsertNewBook = () => {
- + ( + { + const numericValue = parseFloat( + values.value.replace(/,/g, "") + ); + field.onChange(numericValue); + }} + thousandSeparator="." + decimalSeparator="," + valueIsNumericString + customInput={Input} + /> + )} + /> {errors.listedPrice && ( {errors.listedPrice.message} @@ -273,7 +301,25 @@ const InsertNewBook = () => {
- + ( + { + const numericValue = parseFloat( + values.value.replace(/,/g, "") + ); + field.onChange(numericValue); + }} + thousandSeparator="." + decimalSeparator="," + valueIsNumericString + customInput={Input} + /> + )} + /> {errors.sellPrice && ( {errors.sellPrice.message} diff --git a/frontend/app/staff/[staffId]/detail-layout.tsx b/frontend/app/staff/[staffId]/detail-layout.tsx index ab5696b..db2bcd3 100644 --- a/frontend/app/staff/[staffId]/detail-layout.tsx +++ b/frontend/app/staff/[staffId]/detail-layout.tsx @@ -39,6 +39,7 @@ import { useCurrentUser } from "@/hooks/use-user"; import { includesRoles, isAdmin } from "@/lib/utils"; import { useLoading } from "@/hooks/loading-context"; import StaffDetailSkeleton from "@/components/skeleton/staff-detail"; +import { useRouter } from "next/navigation"; const FormSchema = z.object({ name: required, @@ -50,6 +51,7 @@ const PasswordSchema = z.object({ }); const EditStaff = ({ params }: { params: { staffId: string } }) => { + const router = useRouter(); const [role, setRole] = useState(""); const { showLoading, hideLoading } = useLoading(); @@ -134,6 +136,7 @@ const EditStaff = ({ params }: { params: { staffId: string } }) => { description: "Đặt lại mật khẩu nhân viên thành công", }); setOpen(false); + router.refresh(); } }; const onSubmit: SubmitHandler> = async (data) => { @@ -160,6 +163,7 @@ const EditStaff = ({ params }: { params: { staffId: string } }) => { description: "Chỉnh sửa thông tin nhân viên thành công", }); mutate(); + router.refresh(); } }; const handleOpen = (value: boolean) => { @@ -207,6 +211,7 @@ const EditStaff = ({ params }: { params: { staffId: string } }) => { description: "Thay đổi ảnh nhân viên thành công", }); mutate(); + router.refresh(); } } }; @@ -248,6 +253,7 @@ const EditStaff = ({ params }: { params: { staffId: string } }) => { }); setOpen(false); mutate(); + router.refresh(); } }; @@ -272,6 +278,7 @@ const EditStaff = ({ params }: { params: { staffId: string } }) => { }); setOpen(false); mutate(); + router.refresh(); } }; const { currentUser } = useCurrentUser(); diff --git a/frontend/app/stockmanage/import/[importId]/page.tsx b/frontend/app/stockmanage/import/[importId]/page.tsx index 69281f4..45d5385 100644 --- a/frontend/app/stockmanage/import/[importId]/page.tsx +++ b/frontend/app/stockmanage/import/[importId]/page.tsx @@ -46,6 +46,7 @@ const ImportDetail = ({ params }: { params: { importId: string } }) => { description: "Chuyển trạng thái thành công", }); mutate(); + router.refresh(); } }; const { currentUser } = useCurrentUser(); diff --git a/frontend/app/supplier/[supplierId]/detail-layout.tsx b/frontend/app/supplier/[supplierId]/detail-layout.tsx index f469803..bb38dd2 100644 --- a/frontend/app/supplier/[supplierId]/detail-layout.tsx +++ b/frontend/app/supplier/[supplierId]/detail-layout.tsx @@ -3,7 +3,7 @@ import { Card, CardContent } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { useState } from "react"; +import { ChangeEvent, forwardRef, useEffect, useRef, useState } from "react"; import { ImportTable } from "@/components/supplier-manage/import-table"; import { DebtTable } from "@/components/supplier-manage/debt-table"; import { Button } from "@/components/ui/button"; @@ -17,7 +17,7 @@ import { import getSupplier from "@/lib/supplier/getSupplier"; import { zodResolver } from "@hookform/resolvers/zod"; import * as z from "zod"; -import { SubmitHandler, useForm } from "react-hook-form"; +import { Controller, SubmitHandler, useForm } from "react-hook-form"; import { toast } from "@/components/ui/use-toast"; import paySupplier from "@/lib/supplier/paySupplier"; import EditDialog from "@/components/supplier-manage/edit"; @@ -25,22 +25,33 @@ import { useSWRConfig } from "swr"; import { endPoint } from "@/constants"; import { withAuth } from "@/lib/role/withAuth"; import { useCurrentUser } from "@/hooks/use-user"; -import { includesRoles } from "@/lib/utils"; -import NoRole from "@/components/no-role"; +import { includesRoles, toVND } from "@/lib/utils"; import { useLoading } from "@/hooks/loading-context"; import { Skeleton } from "@/components/ui/skeleton"; import DropdownSkeleton from "@/components/skeleton/dropdown-skeleton"; +import { useRouter } from "next/navigation"; +import { NumericFormat } from "react-number-format"; +import React from "react"; + const FormSchema = z.object({ - quantity: z.coerce.number().gte(1, "Giá trị phải lớn hơn 0"), // Force it to be a number + quantity: z.coerce + .number({ invalid_type_error: "Giá trị phải là một số" }) + .gte(1, "Giá trị phải lớn hơn 0") + .refine((value) => Number.isInteger(value), { + message: "Giá trị phải là số nguyên", + }), // Force it to be a number }); + const SupplierDetail = ({ params }: { params: { supplierId: string } }) => { const form = useForm>({ resolver: zodResolver(FormSchema), + defaultValues: { quantity: 0 }, }); const { register, handleSubmit, control, + reset, formState: { errors }, } = form; const { @@ -49,7 +60,9 @@ const SupplierDetail = ({ params }: { params: { supplierId: string } }) => { isError, mutate: mutateSupplier, } = getSupplier(params.supplierId); + const inputRef = useRef(null); + const router = useRouter(); const { mutate } = useSWRConfig(); const { showLoading, hideLoading } = useLoading(); const onSubmit: SubmitHandler> = async ( @@ -76,6 +89,7 @@ const SupplierDetail = ({ params }: { params: { supplierId: string } }) => { }); mutate(`${endPoint}/v1/suppliers/${data.id}/debts?page=${pageIndex}`); mutateSupplier(); + router.refresh(); } setOpenDialog(false); }; @@ -169,7 +183,15 @@ const SupplierDetail = ({ params }: { params: { supplierId: string } }) => { /> ) : null} - + { + if (open) { + reset({ quantity: 0 }); + } + setOpenDialog(open); + }} + > {!currentUser || (currentUser && @@ -207,11 +229,26 @@ const SupplierDetail = ({ params }: { params: { supplierId: string } }) => {
- + ( + { + const numericValue = parseFloat( + values.value.replace(/,/g, "") + ); + field.onChange(numericValue); + }} + thousandSeparator="." + decimalSeparator="," + valueIsNumericString + customInput={Input} + /> + )} + /> + {errors.quantity && ( {errors.quantity.message} @@ -255,7 +292,7 @@ const SupplierDetail = ({ params }: { params: { supplierId: string } }) => {
- +
diff --git a/frontend/components/book-manage/book-title-select-edit.tsx b/frontend/components/book-manage/book-title-select-edit.tsx index b0e72c1..c5a857d 100644 --- a/frontend/components/book-manage/book-title-select-edit.tsx +++ b/frontend/components/book-manage/book-title-select-edit.tsx @@ -31,13 +31,16 @@ const BookTitleSelect = ({ handleTitleSet, titleId, readOnly }: Props) => { const [title, setTitle] = useState(); const { titles, isLoading, isError, mutate } = getAllTitleList(); useEffect(() => { - if (titles) { + if (titles && titleId && titleId !== "") { onSetTitle(titles.data.find((item: BookTitle) => item.id === titleId)); } }, [titles, titleId]); const onSetTitle = (title: BookTitle) => { - setTitle(title); - handleTitleSet(title.id); + if (title) { + setTitle(title); + //loi ne + handleTitleSet(title.id); + } }; const handleTitleAdded = async (value: string) => { const newTitleList = await mutate(); diff --git a/frontend/components/book-manage/create-title-dialog.tsx b/frontend/components/book-manage/create-title-dialog.tsx index b45c43e..a6aa1de 100644 --- a/frontend/components/book-manage/create-title-dialog.tsx +++ b/frontend/components/book-manage/create-title-dialog.tsx @@ -25,6 +25,7 @@ import getAllAuthorList from "@/lib/book/getAllAuthorList"; import getAllCategoryList from "@/lib/book/getAllCategoryList"; import { useLoading } from "@/hooks/loading-context"; import DropdownSkeleton from "../skeleton/dropdown-skeleton"; +import { useRouter } from "next/navigation"; const FormSchema = z.object({ idBook: z.string().max(12, "Tối đa 12 ký tự"), @@ -46,6 +47,7 @@ const CreateTitleDialog = ({ children: React.ReactNode; }) => { const { toast } = useToast(); + const router = useRouter(); const form = useForm>({ resolver: zodResolver(FormSchema), }); @@ -116,6 +118,8 @@ const CreateTitleDialog = ({ description: "Thêm đầu sách mới thành công", }); handleTitleAdded(responseData.data); + router.refresh(); + setOpen(false); } }; diff --git a/frontend/components/customer/create.tsx b/frontend/components/customer/create.tsx index 620dd3e..89e6c99 100644 --- a/frontend/components/customer/create.tsx +++ b/frontend/components/customer/create.tsx @@ -24,7 +24,15 @@ import { useLoading } from "@/hooks/loading-context"; const SupplierSchema = z.object({ id: z.string().max(12, "Tối đa 12 ký tự"), name: required, - email: z.string().email("Email không hợp lệ"), + email: z + .string() + .refine( + (value) => + value === "" || /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value), + { + message: "Email không hợp lệ", + } + ), phone: z.string().regex(phoneRegex, "Số điện thoại không hợp lệ"), }); diff --git a/frontend/components/customer/table.tsx b/frontend/components/customer/table.tsx index 99d2ff8..6697b80 100644 --- a/frontend/components/customer/table.tsx +++ b/frontend/components/customer/table.tsx @@ -139,10 +139,7 @@ export const columns: ColumnDef[] = [ const amount = parseFloat(row.getValue("point")); // Format the amount as a dollar amount - const formatted = new Intl.NumberFormat("vi-VN", { - style: "currency", - currency: "VND", - }).format(amount); + const formatted = amount.toLocaleString("vi-VN"); return
{formatted}
; }, diff --git a/frontend/components/profile.tsx b/frontend/components/profile.tsx index e5cc66b..f9e3e3b 100644 --- a/frontend/components/profile.tsx +++ b/frontend/components/profile.tsx @@ -106,7 +106,7 @@ const Profile = () => { toast({ variant: "success", title: "Thành công", - description: "Đặt lại mật khẩu nhân viên thành công", + description: "Đổi mật khẩu thành công", }); setOpen(false); } diff --git a/frontend/components/skeleton/book-detail-skeleton.tsx b/frontend/components/skeleton/book-detail-skeleton.tsx index ab3b460..a44754d 100644 --- a/frontend/components/skeleton/book-detail-skeleton.tsx +++ b/frontend/components/skeleton/book-detail-skeleton.tsx @@ -9,7 +9,7 @@ const BookDetailSkeleton = () => {

Sách:{" "} - , +

diff --git a/frontend/components/staff/create-staff-dialog.tsx b/frontend/components/staff/create-staff-dialog.tsx index d717e1a..a35f75b 100644 --- a/frontend/components/staff/create-staff-dialog.tsx +++ b/frontend/components/staff/create-staff-dialog.tsx @@ -28,7 +28,7 @@ const StaffSchema = z.object({ name: required, email: z.string().email("Email không hợp lệ"), phone: z.string().regex(phoneRegex, "Số điện thoại không hợp lệ"), - address: required, + address: z.string(), roleId: z.string().min(1, "Không để trống trường này"), img: z.string(), }); @@ -56,26 +56,25 @@ const CreateStaffDialog = () => { const { showLoading, hideLoading } = useLoading(); const router = useRouter(); const onSubmit: SubmitHandler> = async (data) => { - setOpen(false); - if (!image) { - return; - } - let formData = new FormData(); + if (image) { + let formData = new FormData(); - formData.append("file", image); - formData.append("folderName", "avatars"); - showLoading(); - const imgRes = await imageUpload(formData); - if (imgRes.hasOwnProperty("errorKey")) { - toast({ - variant: "destructive", - title: "Có lỗi", - description: imgRes.message, - }); - return; + formData.append("file", image); + formData.append("folderName", "avatars"); + showLoading(); + const imgRes = await imageUpload(formData); + if (imgRes.hasOwnProperty("errorKey")) { + toast({ + variant: "destructive", + title: "Có lỗi", + description: imgRes.message, + }); + return; + } else { + data.img = imgRes.data; + } } - data.img = imgRes.data; const response: Promise = createStaff({ staff: data }); const responseData = await response; hideLoading(); @@ -91,6 +90,8 @@ const CreateStaffDialog = () => { title: "Thành công", description: "Thêm nhân viên thành công", }); + setOpen(false); + router.refresh(); } }; diff --git a/frontend/components/stock-manage/book-insert.tsx b/frontend/components/stock-manage/book-insert.tsx index bc65d20..01aa386 100644 --- a/frontend/components/stock-manage/book-insert.tsx +++ b/frontend/components/stock-manage/book-insert.tsx @@ -4,6 +4,7 @@ import { Input } from "../ui/input"; import { Button } from "../ui/button"; import { Control, + Controller, UseFormReturn, useFieldArray, useWatch, @@ -20,6 +21,7 @@ import { toVND } from "@/lib/utils"; import { AutoComplete } from "../ui/autocomplete"; import getAllBookForSale from "@/lib/book/getAllBookForSale"; import DropdownSkeleton from "../skeleton/dropdown-skeleton"; +import { NumericFormat } from "react-number-format"; const Total = ({ control, }: { @@ -135,10 +137,25 @@ const BookInsert = ({ ({value.id})
- + ( + { + const numericValue = parseFloat( + values.value.replace(/,/g, "") + ); + field.onChange(numericValue); + }} + thousandSeparator="." + decimalSeparator="," + valueIsNumericString + customInput={Input} + /> + )} + /> {errors && errors.details && errors.details[index] && @@ -163,10 +180,27 @@ const BookInsert = ({
- + ( + { + const numericValue = parseFloat( + values.value.replace(/,/g, "") + ); + + field.onChange(numericValue); + }} + defaultValue={book.qtyImport} + thousandSeparator="." + decimalSeparator="," + valueIsNumericString + customInput={Input} + /> + )} + /> {errors && errors.details && errors.details[index] && diff --git a/frontend/components/supplier-manage/create.tsx b/frontend/components/supplier-manage/create.tsx index 095be3c..d84983e 100644 --- a/frontend/components/supplier-manage/create.tsx +++ b/frontend/components/supplier-manage/create.tsx @@ -1,5 +1,5 @@ "use client"; -import { SubmitHandler, useForm } from "react-hook-form"; +import { Controller, SubmitHandler, useForm } from "react-hook-form"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -20,11 +20,20 @@ import { phoneRegex, required } from "@/constants"; import { useCurrentUser } from "@/hooks/use-user"; import { includesRoles } from "@/lib/utils"; import { useLoading } from "@/hooks/loading-context"; +import { NumericFormat } from "react-number-format"; const SupplierSchema = z.object({ id: z.string().max(12, "Tối đa 12 ký tự"), name: required, - email: z.string().email("Email không hợp lệ"), + email: z + .string() + .refine( + (value) => + value === "" || /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value), + { + message: "Email không hợp lệ", + } + ), phone: z.string().regex(phoneRegex, "Số điện thoại không hợp lệ"), debt: z.coerce .number({ invalid_type_error: "Nợ ban đầu phải là một số" }) @@ -46,6 +55,7 @@ const CreateDialog = ({ register, handleSubmit, reset, + control, formState: { errors }, } = useForm>({ resolver: zodResolver(SupplierSchema), @@ -157,7 +167,25 @@ const CreateDialog = ({
- + ( + { + const numericValue = parseFloat( + values.value.replace(/,/g, "") + ); + field.onChange(numericValue); + }} + thousandSeparator="." + decimalSeparator="," + valueIsNumericString + customInput={Input} + /> + )} + /> {errors.debt && ( {errors.debt.message} diff --git a/frontend/components/supplier-manage/export-dialog.tsx b/frontend/components/supplier-manage/export-dialog.tsx index d90eba4..8118e82 100644 --- a/frontend/components/supplier-manage/export-dialog.tsx +++ b/frontend/components/supplier-manage/export-dialog.tsx @@ -27,7 +27,7 @@ const ExportDialog = ({ Xuất file - + Xuất danh sách
@@ -44,7 +44,7 @@ const ExportDialog = ({
diff --git a/frontend/components/ui/dialog.tsx b/frontend/components/ui/dialog.tsx index bc61bf6..46a6dc9 100644 --- a/frontend/components/ui/dialog.tsx +++ b/frontend/components/ui/dialog.tsx @@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<