From 6a97dcf5f6682b2f2599784a73b165fa815610d9 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Fri, 7 Nov 2025 18:15:45 +0530 Subject: [PATCH] added initially expense --- src/components/Dashboard/ExpenseByProject.jsx | 8 +- src/components/Expenses/ActiveFilters.jsx | 50 ++ .../Expenses/ExpenseFilterChips.jsx | 75 +- .../Expenses/ExpenseFilterPanel.jsx | 17 +- src/components/Expenses/ExpenseList.jsx | 122 ++- src/components/Expenses/ExpenseSchema.js | 69 +- src/components/Expenses/ExpenseStatusLogs.jsx | 74 +- src/components/Expenses/Filelist.jsx | 95 +++ src/components/Expenses/ManageExpense.jsx | 28 +- src/components/Expenses/PreviewDocument.jsx | 137 +--- src/components/Expenses/ViewExpense.jsx | 736 ++++++++++-------- src/components/common/TimeLine.jsx | 104 +++ src/components/common/Tooltip.jsx | 24 + ...enseType.jsx => ManageExpenseCategory.jsx} | 17 +- src/components/master/MasterModal.jsx | 6 +- src/hooks/masterHook/useMaster.js | 78 +- src/repositories/MastersRepository.jsx | 10 +- src/utils/constants.jsx | 85 +- 18 files changed, 1058 insertions(+), 677 deletions(-) create mode 100644 src/components/Expenses/ActiveFilters.jsx create mode 100644 src/components/Expenses/Filelist.jsx create mode 100644 src/components/common/TimeLine.jsx create mode 100644 src/components/common/Tooltip.jsx rename src/components/master/{ManageExpenseType.jsx => ManageExpenseCategory.jsx} (86%) diff --git a/src/components/Dashboard/ExpenseByProject.jsx b/src/components/Dashboard/ExpenseByProject.jsx index 1c87ef33..9a0e5a95 100644 --- a/src/components/Dashboard/ExpenseByProject.jsx +++ b/src/components/Dashboard/ExpenseByProject.jsx @@ -1,6 +1,5 @@ import React, { useState, useEffect } from "react"; import Chart from "react-apexcharts"; -import { useExpenseType } from "../../hooks/masterHook/useMaster"; import { useSelector } from "react-redux"; import { useExpenseDataByProject } from "../../hooks/useDashboard_Data"; import { formatCurrency } from "../../utils/appUtils"; @@ -8,6 +7,7 @@ import { formatDate_DayMonth } from "../../utils/dateUtils"; import { useProjectName } from "../../hooks/useProjects"; import { useSelectedProject } from "../../slices/apiDataManager"; import { SpinnerLoader } from "../common/Loader"; +import { useExpenseCategory } from "../../hooks/masterHook/useMaster"; const ExpenseByProject = () => { const projectId = useSelector((store) => store.localVariables.projectId); @@ -19,7 +19,7 @@ const ExpenseByProject = () => { const [chartData, setChartData] = useState({ categories: [], data: [] }); const selectedProject = useSelectedProject(); - const { ExpenseTypes, loading: typeLoading } = useExpenseType(); + const {expenseCategories , loading: typeLoading } = useExpenseCategory(); const { data: expenseApiData, isLoading } = useExpenseDataByProject( projectId, @@ -50,7 +50,7 @@ const ExpenseByProject = () => { const getSelectedTypeName = () => { if (!selectedType) return "All Types"; - const found = ExpenseTypes.find((t) => t.id === selectedType); + const found = expenseCategories?.find((t) => t.id === selectedType); return found ? found.name : "All Types"; }; @@ -157,7 +157,7 @@ const ExpenseByProject = () => { style={{ maxWidth: "200px" }} > - {ExpenseTypes.map((type) => ( + {expenseCategories?.map((type) => ( diff --git a/src/components/Expenses/ActiveFilters.jsx b/src/components/Expenses/ActiveFilters.jsx new file mode 100644 index 00000000..ebd8639d --- /dev/null +++ b/src/components/Expenses/ActiveFilters.jsx @@ -0,0 +1,50 @@ +const ActiveFilters = ({ filters, optionsLookup = {}, onRemove }) => { + const entries = Object.entries(filters || {}); + + return ( +
+ {entries.map(([key, value]) => { + if (!value || (Array.isArray(value) && value.length === 0)) return null; + + if (Array.isArray(value)) { + return value.map((v) => { + const label = optionsLookup[key]?.[v] || v; + return ( + onRemove(key, v)} + > + {label} + + ); + }); + } + + if (typeof value === "boolean") { + return ( + onRemove(key)} + > + {key}: {value ? "Yes" : "No"} + + ); + } + + return ( + + {data?.startDate && data?.endDate + ? `${formatUTCToLocalTime( + data.startDate + )} - ${formatUTCToLocalTime(data.endDate)}` + : "No dates"} + + ); + })} +
+ ); +}; + +export default ActiveFilters; diff --git a/src/components/Expenses/ExpenseFilterChips.jsx b/src/components/Expenses/ExpenseFilterChips.jsx index 66c8a376..c44dd7a5 100644 --- a/src/components/Expenses/ExpenseFilterChips.jsx +++ b/src/components/Expenses/ExpenseFilterChips.jsx @@ -40,44 +40,47 @@ const ExpenseFilterChips = ({ filters, filterData, removeFilterChip }) => { if (!filterChips.length) return null; return ( -
-
-
- {filterChips.map((chip) => ( -
- {/* Chip Label */} - {chip.label}: +
+
+
+ {filterChips.map((chip) => ( +
+ {/* Chip Label */} + {chip.label}: - {/* Chip Items */} -
- {chip.items.map((item) => ( - - {item.name} -
-
- ))} -
-
+ {/* Chip Items */} +
+ {chip.items.map((item) => ( + + {item.name} +
+ ))} +
+
+
+ + + ); }; diff --git a/src/components/Expenses/ExpenseFilterPanel.jsx b/src/components/Expenses/ExpenseFilterPanel.jsx index 00c1a7f0..8b1f8bb6 100644 --- a/src/components/Expenses/ExpenseFilterPanel.jsx +++ b/src/components/Expenses/ExpenseFilterPanel.jsx @@ -46,12 +46,12 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata } projectIds: defaultFilter.projectIds || [], createdByIds: defaultFilter.createdByIds || [], paidById: defaultFilter.paidById || [], - ExpenseTypeIds: defaultFilter.ExpenseTypeIds || [], + ExpenseCategoryIds: defaultFilter.ExpenseCategoryIds || [], isTransactionDate: defaultFilter.isTransactionDate ?? true, startDate: defaultFilter.startDate, endDate: defaultFilter.endDate, }; - }, [status]); + }, [status, selectedProjectId]); const methods = useForm({ resolver: zodResolver(SearchSchema), @@ -96,7 +96,7 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata } endDate: moment.utc(formData.endDate, "DD-MM-YYYY").toISOString(), }); handleGroupBy(selectedGroup.id); - closePanel(); + // closePanel(); }; const onClear = () => { @@ -105,7 +105,7 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata } setSelectedGroup(groupByList[0]); onApply(defaultFilter); handleGroupBy(groupByList[0].id); - closePanel(); + // closePanel(); if (status) { navigate("/expenses", { replace: true }); } @@ -145,10 +145,9 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata } handleGroupBy, selectedGroup.id, appliedStatusId, - selectedProjectId, + selectedProjectId, ]); - if (isLoading || isFetching) return ; if (isError && isFetched) return
Something went wrong Here- {error.message}
; @@ -181,7 +180,6 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata }
- @@ -215,9 +214,9 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata } valueKey="id" /> item.name} valueKey="id" /> diff --git a/src/components/Expenses/ExpenseList.jsx b/src/components/Expenses/ExpenseList.jsx index a8147c45..92fd292b 100644 --- a/src/components/Expenses/ExpenseList.jsx +++ b/src/components/Expenses/ExpenseList.jsx @@ -12,6 +12,7 @@ import { } from "../../utils/constants"; import { formatCurrency, + formatFigure, getColorNameFromHex, useDebounce, } from "../../utils/appUtils"; @@ -26,14 +27,18 @@ import { useNavigate } from "react-router-dom"; const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { const [deletingId, setDeletingId] = useState(null); const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - const { setViewExpense, setManageExpenseModal, filterData, removeFilterChip } = useExpenseContext(); + const { + setViewExpense, + setManageExpenseModal, + filterData, + removeFilterChip, + } = useExpenseContext(); const IsExpenseEditable = useHasUserPermission(); const IsExpesneApprpve = useHasUserPermission(APPROVE_EXPENSE); const [currentPage, setCurrentPage] = useState(1); const debouncedSearch = useDebounce(searchText, 500); const navigate = useNavigate(); - const { mutate: DeleteExpense, isPending } = useDeleteExpense(); const { data, isLoading, isError, isInitialLoading, error } = useExpenseList( ITEMS_PER_PAGE, @@ -80,8 +85,9 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { displayField = "Status"; break; case "submittedBy": - key = `${item?.createdBy?.firstName ?? ""} ${item.createdBy?.lastName ?? "" - }`.trim(); + key = `${item?.createdBy?.firstName ?? ""} ${ + item.createdBy?.lastName ?? "" + }`.trim(); displayField = "Submitted By"; break; case "project": @@ -92,8 +98,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { key = item?.paymentMode?.name || "Unknown Mode"; displayField = "Payment Mode"; break; - case "expensesType": - key = item?.expensesType?.name || "Unknown Type"; + case "expenseCategory": + key = item?.expenseCategory?.name || "Unknown Type"; displayField = "Expense Category"; break; case "createdAt": @@ -123,9 +129,9 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { align: "text-start mx-2", }, { - key: "expensesType", + key: "expensesCategory", label: "Expense Category", - getValue: (e) => e.expensesType?.name || "N/A", + getValue: (e) => e.expenseCategory?.name || "N/A", align: "text-start", }, { @@ -139,11 +145,14 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { label: "Submitted By", align: "text-start", getValue: (e) => - `${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? "" - }`.trim() || "N/A", + `${e.createdBy?.firstName ?? ""} ${ + e.createdBy?.lastName ?? "" + }`.trim() || "N/A", customRender: (e) => ( -
navigate(`/employee/${e.createdBy?.id}`)}> +
navigate(`/employee/${e.createdBy?.id}`)} + > { lastName={e.createdBy?.lastName} /> - {`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? "" - }`.trim() || "N/A"} + {`${e.createdBy?.firstName ?? ""} ${ + e.createdBy?.lastName ?? "" + }`.trim() || "N/A"}
), @@ -166,7 +176,15 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { { key: "amount", label: "Amount", - getValue: (e) => <>{formatCurrency(e?.amount)}, + getValue: (e) => ( + <> + {" "} + {formatFigure(e?.amount, { + type: "currency", + currency: e?.currency?.currencyCode, + })} + + ), isAlwaysVisible: true, align: "text-end", }, @@ -176,16 +194,26 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { align: "text-center", getValue: (e) => ( {e.status?.name || "Unknown"} ), }, ]; - - if (isInitialLoading && !data) return ; + const headers = [ + "Expense Category", + "Payment Mode", + "Submitted By", + "Submitted", + "Amount", + "Status", + "Action", + ]; + if (isInitialLoading && !data) + return ; if (isError) return
{error?.message}
; const grouped = groupBy @@ -209,6 +237,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { expense?.status?.id === EXPENSE_DRAFT && expense?.createdBy?.id === SelfId ); }; + return ( <> {IsDeleteModalOpen && ( @@ -245,10 +274,10 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { (col.isAlwaysVisible || groupBy !== col.key) && ( -
{col.label}
+
{col.label}
) )} @@ -263,12 +292,12 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { -
+
{" "} - + {displayField} :{" "} {" "} - + {IsGroupedByDate ? formatUTCToLocalTime(key) : key} @@ -283,16 +312,37 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { (col.isAlwaysVisible || groupBy !== col.key) && ( - {col.customRender - ? col.customRender(expense) - : col.getValue(expense)} +
+ {col.customRender + ? col.customRender(expense) + : col.getValue(expense)} +
) )} -
+
@@ -374,15 +424,15 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { )} - {data?.data?.length > 0 && ( - - )}
+ {data?.data?.length > 0 && ( + + )}
); diff --git a/src/components/Expenses/ExpenseSchema.js b/src/components/Expenses/ExpenseSchema.js index b1339228..cad4cb31 100644 --- a/src/components/Expenses/ExpenseSchema.js +++ b/src/components/Expenses/ExpenseSchema.js @@ -1,4 +1,6 @@ import { z } from "zod"; +import { localToUtc } from "../../utils/appUtils"; +import { DEFAULT_CURRENCY } from "../../utils/constants"; const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB const ALLOWED_TYPES = [ @@ -8,24 +10,25 @@ const ALLOWED_TYPES = [ "image/jpeg", ]; -export const ExpenseSchema = (expenseTypes) => { +export const ExpenseSchema = (ExpenseCategories) => { return z .object({ projectId: z.string().min(1, { message: "Project is required" }), - expensesTypeId: z + expenseCategoryId: z .string() .min(1, { message: "Expense type is required" }), paymentModeId: z.string().min(1, { message: "Payment mode is required" }), paidById: z.string().min(1, { message: "Employee name is required" }), - transactionDate: z - .string() - .min(1, { message: "Date is required" }) - , + transactionDate: z.string().min(1, { message: "Date is required" }), transactionId: z.string().optional(), description: z.string().min(1, { message: "Description is required" }), location: z.string().min(1, { message: "Location is required" }), supplerName: z.string().min(1, { message: "Supplier name is required" }), - gstNumber :z.string().optional(), + gstNumber: z.string().optional(), + currencyId: z + .string() + .min(1, { message: "currency is required" }) + .default(DEFAULT_CURRENCY), amount: z.coerce .number({ invalid_type_error: "Amount is required and must be a number", @@ -54,8 +57,6 @@ export const ExpenseSchema = (expenseTypes) => { }) ) .nonempty({ message: "At least one file attachment is required" }), - - }) .refine( (data) => { @@ -68,9 +69,14 @@ export const ExpenseSchema = (expenseTypes) => { path: ["paidById"], } ) - .superRefine((data, ctx) => { - const expenseType = expenseTypes.find((et) => et.id === data.expensesTypeId); - if (expenseType?.noOfPersonsRequired && (!data.noOfPersons || data.noOfPersons < 1)) { + .superRefine((data, ctx) => { + const ExpenseCategory = ExpenseCategories.find( + (et) => et.id === data.expenseCategoryId + ); + if ( + ExpenseCategory?.noOfPersonsRequired && + (!data.noOfPersons || data.noOfPersons < 1) + ) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "No. of Persons is required and must be at least 1", @@ -82,7 +88,7 @@ export const ExpenseSchema = (expenseTypes) => { export const defaultExpense = { projectId: "", - expensesTypeId: "", + expenseCategoryId: "", paymentModeId: "", paidById: "", transactionDate: "", @@ -92,12 +98,15 @@ export const defaultExpense = { supplerName: "", amount: "", noOfPersons: "", - gstNumber:"", + gstNumber: "", + currencyId: DEFAULT_CURRENCY, billAttachments: [], }; - -export const ExpenseActionScheam = (isReimbursement = false) => { +export const ExpenseActionScheam = ( + isReimbursement = false, + transactionDate +) => { return z .object({ comment: z.string().min(1, { message: "Please leave comment" }), @@ -105,6 +114,9 @@ export const ExpenseActionScheam = (isReimbursement = false) => { reimburseTransactionId: z.string().nullable().optional(), reimburseDate: z.string().nullable().optional(), reimburseById: z.string().nullable().optional(), + tdsPercentage: z.string().nullable().optional(), + baseAmount: z.string().nullable().optional(), + taxAmount: z.string().nullable().optional(), }) .superRefine((data, ctx) => { if (isReimbursement) { @@ -122,6 +134,7 @@ export const ExpenseActionScheam = (isReimbursement = false) => { message: "Reimburse Date is required", }); } + if (!data.reimburseById) { ctx.addIssue({ code: z.ZodIssueCode.custom, @@ -129,26 +142,42 @@ export const ExpenseActionScheam = (isReimbursement = false) => { message: "Reimburse By is required", }); } + if (!data.baseAmount) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["baseAmount"], + message: "Base Amount i required", + }); + } + if (!data.taxAmount) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["taxAmount"], + message: "Tax is required", + }); + } } }); }; - export const defaultActionValues = { +export const defaultActionValues = { comment: "", statusId: "", reimburseTransactionId: null, reimburseDate: null, reimburseById: null, + tdsPercentage: null, + baseAmount:null, + taxAmount: null, }; - - export const SearchSchema = z.object({ projectIds: z.array(z.string()).optional(), statusIds: z.array(z.string()).optional(), createdByIds: z.array(z.string()).optional(), paidById: z.array(z.string()).optional(), + ExpenseCategoryIds: z.array(z.string()).optional(), startDate: z.string().optional(), endDate: z.string().optional(), isTransactionDate: z.boolean().default(true), @@ -159,8 +188,8 @@ export const defaultFilter = { statusIds: [], createdByIds: [], paidById: [], + ExpenseCategoryIds: [], isTransactionDate: true, startDate: null, endDate: null, }; - diff --git a/src/components/Expenses/ExpenseStatusLogs.jsx b/src/components/Expenses/ExpenseStatusLogs.jsx index 575508db..d5014a8b 100644 --- a/src/components/Expenses/ExpenseStatusLogs.jsx +++ b/src/components/Expenses/ExpenseStatusLogs.jsx @@ -1,10 +1,11 @@ -import { useState,useMemo } from "react"; +import { useState, useMemo } from "react"; import Avatar from "../common/Avatar"; import { formatUTCToLocalTime } from "../../utils/dateUtils"; - - +import Timeline from "../common/TimeLine"; +import moment from "moment"; +import { getColorNameFromHex } from "../../utils/appUtils"; const ExpenseStatusLogs = ({ data }) => { - const [visibleCount, setVisibleCount] = useState(4); + const sortedLogs = useMemo(() => { if (!data?.expenseLogs) return []; @@ -13,56 +14,35 @@ const ExpenseStatusLogs = ({ data }) => { ); }, [data?.expenseLogs]); - const logsToShow = sortedLogs.slice(0, visibleCount); + const timelineData = useMemo(() => { + return sortedLogs.map((log, index) => ({ + id: index + 1, + title: log.action || "Status Updated", + description: log.comment || "", + timeAgo: log.updateAt, + color: getColorNameFromHex(log.nextStatus?.color) || "primary", + users: log.updatedBy + ? [ + { + firstName: log.updatedBy.firstName || "", + lastName: log?.updatedBy?.lastName || "", + role: log.updatedBy.jobRoleName || "", + avatar: log.updatedBy.photo, + }, + ] + : [], + })); + }, [sortedLogs]); const handleShowMore = () => { setVisibleCount((prev) => prev + 4); }; return ( - <> -
- {logsToShow.map((log) => ( -
- - -
-
-
- {`${log.updatedBy.firstName} ${log.updatedBy.lastName}`} - - {log.action} - - - {formatUTCToLocalTime(log.updateAt,true)} - -
-
- {log.comment} -
-
-
-
- ))} -
- - {sortedLogs.length > visibleCount && ( -
- -
- )} - +
+ +
); }; - export default ExpenseStatusLogs; diff --git a/src/components/Expenses/Filelist.jsx b/src/components/Expenses/Filelist.jsx new file mode 100644 index 00000000..81786057 --- /dev/null +++ b/src/components/Expenses/Filelist.jsx @@ -0,0 +1,95 @@ +import React from "react"; +import { formatFileSize, getIconByFileType } from "../../utils/appUtils"; +import Tooltip from "../common/Tooltip"; + +const Filelist = ({ files, removeFile, expenseToEdit }) => { + return ( +
+ {files + .filter((file) => { + if (expenseToEdit) { + return file.isActive; + } + return true; + }) + .map((file, idx) => ( +
+
+ {/* File icon and info */} +
+ + +
+ + {file.fileName} + + + {file.fileSize ? formatFileSize(file.fileSize) : ""} + +
+
+ + {/* Delete icon */} + + { + e.preventDefault(); + removeFile(expenseToEdit ? file.documentId : idx); + }} + > + +
+
+ ))} +
+ + ); +}; + +export default Filelist; +export const FilelistView = ({ files, viewFile }) => { + return ( +
+ {files?.map((file, idx) => ( +
+
+ {/* File icon and info */} +
+ + +
{ + e.preventDefault(); + viewFile({ + IsOpen: true, + Image: file.preSignedUrl, + }); + }} + > + + {file.fileName} + + + + {" "} + {file.fileSize ? formatFileSize(file.fileSize) : ""} + + +
+
+
+
+ ))} +
+ ); +}; \ No newline at end of file diff --git a/src/components/Expenses/ManageExpense.jsx b/src/components/Expenses/ManageExpense.jsx index 9b14bfcf..7ea269ee 100644 --- a/src/components/Expenses/ManageExpense.jsx +++ b/src/components/Expenses/ManageExpense.jsx @@ -7,8 +7,8 @@ import { useProjectName } from "../../hooks/useProjects"; import { useDispatch, useSelector } from "react-redux"; import { changeMaster } from "../../slices/localVariablesSlice"; import useMaster, { + useExpenseCategory, useExpenseStatus, - useExpenseType, usePaymentMode, } from "../../hooks/masterHook/useMaster"; import { @@ -39,11 +39,11 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { const [ExpenseType, setExpenseType] = useState(); const dispatch = useDispatch(); const { - ExpenseTypes, + expenseCategories, loading: ExpenseLoading, error: ExpenseError, - } = useExpenseType(); - const schema = ExpenseSchema(ExpenseTypes); + } = useExpenseCategory(); + const schema = ExpenseSchema(expenseCategories); const { register, handleSubmit, @@ -146,7 +146,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { if (expenseToEdit && data) { reset({ projectId: data.project.id || "", - expensesTypeId: data.expensesType.id || "", + expenseCategoryId: data?.expenseCategory?.id || "", paymentModeId: data.paymentMode.id || "", paidById: data.paidBy.id || "", transactionDate: data.transactionDate?.slice(0, 10) || "", @@ -192,11 +192,11 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { CreateExpense(payload); } }; - const ExpenseTypeId = watch("expensesTypeId"); + const expenseCategoryId = watch("expenseCategoryId"); useEffect(() => { - setExpenseType(ExpenseTypes?.find((type) => type.id === ExpenseTypeId)); - }, [ExpenseTypeId]); + setExpenseType(expenseCategories?.find((type) => type.id === expenseCategoryId)); + }, [expenseCategoryId]); const handleClose = () => { reset(); @@ -237,13 +237,13 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
-
diff --git a/src/components/Expenses/PreviewDocument.jsx b/src/components/Expenses/PreviewDocument.jsx index 31f50991..7ea0c3ec 100644 --- a/src/components/Expenses/PreviewDocument.jsx +++ b/src/components/Expenses/PreviewDocument.jsx @@ -1,137 +1,54 @@ -import { useState, useRef ,useEffect} from "react"; +import { useState } from "react"; const PreviewDocument = ({ imageUrl }) => { const [loading, setLoading] = useState(true); const [rotation, setRotation] = useState(0); - const [zoom, setZoom] = useState(1); - const [position, setPosition] = useState({ x: 0, y: 0 }); - const [isDragging, setIsDragging] = useState(false); - const [startPos, setStartPos] = useState({ x: 0, y: 0 }); - const containerRef = useRef(null); - - // Zoom handlers - const handleZoomIn = () => setZoom((prev) => Math.min(prev + 0.2, 3)); - const handleZoomOut = () => setZoom((prev) => Math.max(prev - 0.2, 0.5)); - - // Mouse wheel zoom - const handleWheel = (e) => { - e.preventDefault(); - const delta = e.deltaY > 0 ? -0.1 : 0.1; - setZoom((prev) => Math.min(Math.max(prev + delta, 0.5), 3)); - }; - - useEffect(() => { - const container = containerRef.current; - if (!container) return; - - container.addEventListener("wheel", handleWheel, { passive: false }); - - return () => { - container.removeEventListener("wheel", handleWheel); - }; - }, []); - const handleMouseDown = (e) => { - if (zoom <= 1) return; - setIsDragging(true); - setStartPos({ - x: e.clientX - position.x, - y: e.clientY - position.y, - }); - }; - - const handleMouseMove = (e) => { - if (!isDragging) return; - setPosition({ - x: e.clientX - startPos.x, - y: e.clientY - startPos.y, - }); - }; - - const handleMouseUp = () => setIsDragging(false); - const handleMouseLeave = () => setIsDragging(false); - - const handleReset = () => { - setRotation(0); - setZoom(1); - setPosition({ x: 0, y: 0 }); - }; return ( - <> -
+ <> +
setRotation((prev) => prev + 90)} > - - -
+
+ + {loading && ( +
Loading...
+ )} -
1 ? (isDragging ? "grabbing" : "grab") : "default", - userSelect: "none", - position: "relative", - }} - > - {loading && ( -
- Loading... -
- )} +
Preview setLoading(false)} + alt="Full View" + className="img-fluid" style={{ - transform: `translate(${position.x}px, ${position.y}px) rotate(${rotation}deg) scale(${zoom})`, - transition: isDragging ? "none" : "transform 0.3s ease", + maxHeight: "80vh", objectFit: "contain", - maxWidth: "100%", - maxHeight: "100%", display: loading ? "none" : "block", - pointerEvents: "none", + transform: `rotate(${rotation}deg)`, + transition: "transform 0.3s ease", }} + onLoad={() => setLoading(false)} />
- {/*
+
-
*/} - +
+
+ ); }; export default PreviewDocument; - - - diff --git a/src/components/Expenses/ViewExpense.jsx b/src/components/Expenses/ViewExpense.jsx index cd608dfe..160eb290 100644 --- a/src/components/Expenses/ViewExpense.jsx +++ b/src/components/Expenses/ViewExpense.jsx @@ -10,6 +10,8 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { defaultActionValues, ExpenseActionScheam } from "./ExpenseSchema"; import { useExpenseContext } from "../../pages/Expense/ExpensePage"; import { + formatCurrency, + formatFigure, getColorNameFromHex, getIconByFileType, localToUtc, @@ -42,7 +44,7 @@ const ViewExpense = ({ ExpenseId }) => { const IsReview = useHasUserPermission(REVIEW_EXPENSE); const [imageLoaded, setImageLoaded] = useState({}); const { setDocumentView } = useExpenseContext(); - const ActionSchema = ExpenseActionScheam(IsPaymentProcess) ?? z.object({}); + const ActionSchema = ExpenseActionScheam(IsPaymentProcess,data?.createdAt) ?? z.object({}); const navigate = useNavigate(); const { register, @@ -95,7 +97,7 @@ const ViewExpense = ({ ExpenseId }) => { const onSubmit = (formData) => { const Payload = { ...formData, - reimburseDate: localToUtc(formData.reimburseDate), + reimburseDate:localToUtc(formData.reimburseDate), expenseId: ExpenseId, comment: formData.comment, }; @@ -107,366 +109,430 @@ const ViewExpense = ({ ExpenseId }) => { const handleImageLoad = (id) => { setImageLoaded((prev) => ({ ...prev, [id]: true })); }; - + console.log(errors) return (
-
-
-
Expense Details
-
-
-
-
{data?.description}
-
- {/* Row 1 */} -
-
- -
- {formatUTCToLocalTime(data?.transactionDate)} -
-
-
-
-
- -
{data?.expensesType?.name}
-
-
+
+
Expense Details
+
- {/* Row 2 */} -
-
- -
{data?.supplerName}
-
-
-
-
- -
₹ {data.amount}
-
-
- - {/* Row 3 */} -
-
- -
{data?.paymentMode?.name}
-
-
- {data?.gstNumber && ( -
-
-