added initially expense

This commit is contained in:
pramod.mahajan 2025-11-07 18:15:45 +05:30
parent dcbb4a3997
commit 6a97dcf5f6
18 changed files with 1058 additions and 677 deletions

View File

@ -1,6 +1,5 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import Chart from "react-apexcharts"; import Chart from "react-apexcharts";
import { useExpenseType } from "../../hooks/masterHook/useMaster";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { useExpenseDataByProject } from "../../hooks/useDashboard_Data"; import { useExpenseDataByProject } from "../../hooks/useDashboard_Data";
import { formatCurrency } from "../../utils/appUtils"; import { formatCurrency } from "../../utils/appUtils";
@ -8,6 +7,7 @@ import { formatDate_DayMonth } from "../../utils/dateUtils";
import { useProjectName } from "../../hooks/useProjects"; import { useProjectName } from "../../hooks/useProjects";
import { useSelectedProject } from "../../slices/apiDataManager"; import { useSelectedProject } from "../../slices/apiDataManager";
import { SpinnerLoader } from "../common/Loader"; import { SpinnerLoader } from "../common/Loader";
import { useExpenseCategory } from "../../hooks/masterHook/useMaster";
const ExpenseByProject = () => { const ExpenseByProject = () => {
const projectId = useSelector((store) => store.localVariables.projectId); const projectId = useSelector((store) => store.localVariables.projectId);
@ -19,7 +19,7 @@ const ExpenseByProject = () => {
const [chartData, setChartData] = useState({ categories: [], data: [] }); const [chartData, setChartData] = useState({ categories: [], data: [] });
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
const { ExpenseTypes, loading: typeLoading } = useExpenseType(); const {expenseCategories , loading: typeLoading } = useExpenseCategory();
const { data: expenseApiData, isLoading } = useExpenseDataByProject( const { data: expenseApiData, isLoading } = useExpenseDataByProject(
projectId, projectId,
@ -50,7 +50,7 @@ const ExpenseByProject = () => {
const getSelectedTypeName = () => { const getSelectedTypeName = () => {
if (!selectedType) return "All Types"; 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"; return found ? found.name : "All Types";
}; };
@ -157,7 +157,7 @@ const ExpenseByProject = () => {
style={{ maxWidth: "200px" }} style={{ maxWidth: "200px" }}
> >
<option value="">All Types</option> <option value="">All Types</option>
{ExpenseTypes.map((type) => ( {expenseCategories?.map((type) => (
<option key={type.id} value={type.id}> <option key={type.id} value={type.id}>
{type.name} {type.name}
</option> </option>

View File

@ -0,0 +1,50 @@
const ActiveFilters = ({ filters, optionsLookup = {}, onRemove }) => {
const entries = Object.entries(filters || {});
return (
<div className="d-flex flex-wrap gap-2">
{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 (
<span
key={`${key}-${v}`}
className="badge bg-label-primary cursor-pointer"
onClick={() => onRemove(key, v)}
>
{label} <i className="bx bx-x ms-1"></i>
</span>
);
});
}
if (typeof value === "boolean") {
return (
<span
key={key}
className="badge bg-label-success cursor-pointer"
onClick={() => onRemove(key)}
>
{key}: {value ? "Yes" : "No"} <i className="bx bx-x ms-1"></i>
</span>
);
}
return (
<span className="badge bg-primary">
{data?.startDate && data?.endDate
? `${formatUTCToLocalTime(
data.startDate
)} - ${formatUTCToLocalTime(data.endDate)}`
: "No dates"}
</span>
);
})}
</div>
);
};
export default ActiveFilters;

View File

@ -40,7 +40,7 @@ const ExpenseFilterChips = ({ filters, filterData, removeFilterChip }) => {
if (!filterChips.length) return null; if (!filterChips.length) return null;
return ( return (
<div className="row"> <div className="row">
<div className="col-12"> <div className="col-12">
<div className="d-flex flex-wrap align-items-start gap-1 text-start"> <div className="d-flex flex-wrap align-items-start gap-1 text-start">
{filterChips.map((chip) => ( {filterChips.map((chip) => (
@ -77,7 +77,10 @@ const ExpenseFilterChips = ({ filters, filterData, removeFilterChip }) => {
))} ))}
</div> </div>
</div> </div>
</div> </div>
); );
}; };

View File

@ -46,12 +46,12 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata }
projectIds: defaultFilter.projectIds || [], projectIds: defaultFilter.projectIds || [],
createdByIds: defaultFilter.createdByIds || [], createdByIds: defaultFilter.createdByIds || [],
paidById: defaultFilter.paidById || [], paidById: defaultFilter.paidById || [],
ExpenseTypeIds: defaultFilter.ExpenseTypeIds || [], ExpenseCategoryIds: defaultFilter.ExpenseCategoryIds || [],
isTransactionDate: defaultFilter.isTransactionDate ?? true, isTransactionDate: defaultFilter.isTransactionDate ?? true,
startDate: defaultFilter.startDate, startDate: defaultFilter.startDate,
endDate: defaultFilter.endDate, endDate: defaultFilter.endDate,
}; };
}, [status]); }, [status, selectedProjectId]);
const methods = useForm({ const methods = useForm({
resolver: zodResolver(SearchSchema), resolver: zodResolver(SearchSchema),
@ -96,7 +96,7 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata }
endDate: moment.utc(formData.endDate, "DD-MM-YYYY").toISOString(), endDate: moment.utc(formData.endDate, "DD-MM-YYYY").toISOString(),
}); });
handleGroupBy(selectedGroup.id); handleGroupBy(selectedGroup.id);
closePanel(); // closePanel();
}; };
const onClear = () => { const onClear = () => {
@ -105,7 +105,7 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata }
setSelectedGroup(groupByList[0]); setSelectedGroup(groupByList[0]);
onApply(defaultFilter); onApply(defaultFilter);
handleGroupBy(groupByList[0].id); handleGroupBy(groupByList[0].id);
closePanel(); // closePanel();
if (status) { if (status) {
navigate("/expenses", { replace: true }); navigate("/expenses", { replace: true });
} }
@ -148,7 +148,6 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata }
selectedProjectId, selectedProjectId,
]); ]);
if (isLoading || isFetching) return <ExpenseFilterSkeleton />; if (isLoading || isFetching) return <ExpenseFilterSkeleton />;
if (isError && isFetched) if (isError && isFetched)
return <div>Something went wrong Here- {error.message} </div>; return <div>Something went wrong Here- {error.message} </div>;
@ -181,7 +180,6 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata }
</button> </button>
</div> </div>
</div> </div>
<label className="fw-semibold">Choose Date Range:</label>
<DateRangePicker1 <DateRangePicker1
placeholder="DD-MM-YYYY To DD-MM-YYYY" placeholder="DD-MM-YYYY To DD-MM-YYYY"
startField="startDate" startField="startDate"
@ -189,6 +187,7 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata }
resetSignal={resetKey} resetSignal={resetKey}
defaultRange={false} defaultRange={false}
maxDate={new Date()} maxDate={new Date()}
className="w-100"
/> />
</div> </div>
@ -215,9 +214,9 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata }
valueKey="id" valueKey="id"
/> />
<SelectMultiple <SelectMultiple
name="ExpenseTypeIds" name="ExpenseCategoryIds"
label="Category :" label="Category :"
options={data.expensesType} options={data.expenseCategory}
labelKey={(item) => item.name} labelKey={(item) => item.name}
valueKey="id" valueKey="id"
/> />

View File

@ -12,6 +12,7 @@ import {
} from "../../utils/constants"; } from "../../utils/constants";
import { import {
formatCurrency, formatCurrency,
formatFigure,
getColorNameFromHex, getColorNameFromHex,
useDebounce, useDebounce,
} from "../../utils/appUtils"; } from "../../utils/appUtils";
@ -26,14 +27,18 @@ import { useNavigate } from "react-router-dom";
const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
const [deletingId, setDeletingId] = useState(null); const [deletingId, setDeletingId] = useState(null);
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const { setViewExpense, setManageExpenseModal, filterData, removeFilterChip } = useExpenseContext(); const {
setViewExpense,
setManageExpenseModal,
filterData,
removeFilterChip,
} = useExpenseContext();
const IsExpenseEditable = useHasUserPermission(); const IsExpenseEditable = useHasUserPermission();
const IsExpesneApprpve = useHasUserPermission(APPROVE_EXPENSE); const IsExpesneApprpve = useHasUserPermission(APPROVE_EXPENSE);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const debouncedSearch = useDebounce(searchText, 500); const debouncedSearch = useDebounce(searchText, 500);
const navigate = useNavigate(); const navigate = useNavigate();
const { mutate: DeleteExpense, isPending } = useDeleteExpense(); const { mutate: DeleteExpense, isPending } = useDeleteExpense();
const { data, isLoading, isError, isInitialLoading, error } = useExpenseList( const { data, isLoading, isError, isInitialLoading, error } = useExpenseList(
ITEMS_PER_PAGE, ITEMS_PER_PAGE,
@ -80,7 +85,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
displayField = "Status"; displayField = "Status";
break; break;
case "submittedBy": case "submittedBy":
key = `${item?.createdBy?.firstName ?? ""} ${item.createdBy?.lastName ?? "" key = `${item?.createdBy?.firstName ?? ""} ${
item.createdBy?.lastName ?? ""
}`.trim(); }`.trim();
displayField = "Submitted By"; displayField = "Submitted By";
break; break;
@ -92,8 +98,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
key = item?.paymentMode?.name || "Unknown Mode"; key = item?.paymentMode?.name || "Unknown Mode";
displayField = "Payment Mode"; displayField = "Payment Mode";
break; break;
case "expensesType": case "expenseCategory":
key = item?.expensesType?.name || "Unknown Type"; key = item?.expenseCategory?.name || "Unknown Type";
displayField = "Expense Category"; displayField = "Expense Category";
break; break;
case "createdAt": case "createdAt":
@ -123,9 +129,9 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
align: "text-start mx-2", align: "text-start mx-2",
}, },
{ {
key: "expensesType", key: "expensesCategory",
label: "Expense Category", label: "Expense Category",
getValue: (e) => e.expensesType?.name || "N/A", getValue: (e) => e.expenseCategory?.name || "N/A",
align: "text-start", align: "text-start",
}, },
{ {
@ -139,11 +145,14 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
label: "Submitted By", label: "Submitted By",
align: "text-start", align: "text-start",
getValue: (e) => getValue: (e) =>
`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? "" `${e.createdBy?.firstName ?? ""} ${
e.createdBy?.lastName ?? ""
}`.trim() || "N/A", }`.trim() || "N/A",
customRender: (e) => ( customRender: (e) => (
<div className="d-flex align-items-center cursor-pointer" <div
onClick={() => navigate(`/employee/${e.createdBy?.id}`)}> className="d-flex align-items-center cursor-pointer"
onClick={() => navigate(`/employee/${e.createdBy?.id}`)}
>
<Avatar <Avatar
size="xs" size="xs"
classAvatar="m-0" classAvatar="m-0"
@ -151,7 +160,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
lastName={e.createdBy?.lastName} lastName={e.createdBy?.lastName}
/> />
<span className="text-truncate"> <span className="text-truncate">
{`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? "" {`${e.createdBy?.firstName ?? ""} ${
e.createdBy?.lastName ?? ""
}`.trim() || "N/A"} }`.trim() || "N/A"}
</span> </span>
</div> </div>
@ -166,7 +176,15 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
{ {
key: "amount", key: "amount",
label: "Amount", label: "Amount",
getValue: (e) => <>{formatCurrency(e?.amount)}</>, getValue: (e) => (
<>
{" "}
{formatFigure(e?.amount, {
type: "currency",
currency: e?.currency?.currencyCode,
})}
</>
),
isAlwaysVisible: true, isAlwaysVisible: true,
align: "text-end", align: "text-end",
}, },
@ -176,7 +194,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
align: "text-center", align: "text-center",
getValue: (e) => ( getValue: (e) => (
<span <span
className={`badge bg-label-${getColorNameFromHex(e?.status?.color) || "secondary" className={`badge bg-label-${
getColorNameFromHex(e?.status?.color) || "secondary"
}`} }`}
> >
{e.status?.name || "Unknown"} {e.status?.name || "Unknown"}
@ -184,8 +203,17 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
), ),
}, },
]; ];
const headers = [
if (isInitialLoading && !data) return <ExpenseTableSkeleton />; "Expense Category",
"Payment Mode",
"Submitted By",
"Submitted",
"Amount",
"Status",
"Action",
];
if (isInitialLoading && !data)
return <ExpenseTableSkeleton headers={headers} />;
if (isError) return <div>{error?.message}</div>; if (isError) return <div>{error?.message}</div>;
const grouped = groupBy const grouped = groupBy
@ -209,6 +237,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
expense?.status?.id === EXPENSE_DRAFT && expense?.createdBy?.id === SelfId expense?.status?.id === EXPENSE_DRAFT && expense?.createdBy?.id === SelfId
); );
}; };
return ( return (
<> <>
{IsDeleteModalOpen && ( {IsDeleteModalOpen && (
@ -245,10 +274,10 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
(col.isAlwaysVisible || groupBy !== col.key) && ( (col.isAlwaysVisible || groupBy !== col.key) && (
<th <th
key={col.key} key={col.key}
className={`sorting d-table-cell`} className={`sorting d-table-cell `}
aria-sort="descending" aria-sort="descending"
> >
<div className={`${col.align}`}>{col.label}</div> <div className={`${col.align} `}>{col.label}</div>
</th> </th>
) )
)} )}
@ -263,9 +292,9 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
<React.Fragment key={key}> <React.Fragment key={key}>
<tr className="tr-group text-dark"> <tr className="tr-group text-dark">
<td colSpan={8} className="text-start"> <td colSpan={8} className="text-start">
<div className="d-flex align-items-center"> <div className="d-flex align-items-center px-2">
{" "} {" "}
<small className="fs-6 ms-2 py-1"> <small className="fs-6 py-1">
{displayField} :{" "} {displayField} :{" "}
</small>{" "} </small>{" "}
<small className="fs-6 ms-3"> <small className="fs-6 ms-3">
@ -283,16 +312,37 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
(col.isAlwaysVisible || groupBy !== col.key) && ( (col.isAlwaysVisible || groupBy !== col.key) && (
<td <td
key={col.key} key={col.key}
className={`d-table-cell ${col.align ?? ""}`} className={`d-table-cell ml-2 ${
col.align ?? ""
} `}
>
<div
className={`d-flex px-2 ${
col.key === "status"
? "justify-content-center"
: ""
}
${
col.key === "amount"
? "justify-content-end"
: ""
}
${
col.key === "submitted"
? "justify-content-center"
: ""
}
`}
> >
{col.customRender {col.customRender
? col.customRender(expense) ? col.customRender(expense)
: col.getValue(expense)} : col.getValue(expense)}
</div>
</td> </td>
) )
)} )}
<td className="sticky-action-column bg-white"> <td className="sticky-action-column bg-white">
<div className="d-flex justify-content-center gap-2"> <div className="d-flex flex-row gap-2">
<i <i
className="bx bx-show text-primary cursor-pointer" className="bx bx-show text-primary cursor-pointer"
onClick={() => onClick={() =>
@ -374,6 +424,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
)} )}
</tbody> </tbody>
</table> </table>
</div>
</div>
{data?.data?.length > 0 && ( {data?.data?.length > 0 && (
<Pagination <Pagination
currentPage={currentPage} currentPage={currentPage}
@ -382,8 +434,6 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
/> />
)} )}
</div> </div>
</div>
</div>
</> </>
); );
}; };

View File

@ -1,4 +1,6 @@
import { z } from "zod"; import { z } from "zod";
import { localToUtc } from "../../utils/appUtils";
import { DEFAULT_CURRENCY } from "../../utils/constants";
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
const ALLOWED_TYPES = [ const ALLOWED_TYPES = [
@ -8,24 +10,25 @@ const ALLOWED_TYPES = [
"image/jpeg", "image/jpeg",
]; ];
export const ExpenseSchema = (expenseTypes) => { export const ExpenseSchema = (ExpenseCategories) => {
return z return z
.object({ .object({
projectId: z.string().min(1, { message: "Project is required" }), projectId: z.string().min(1, { message: "Project is required" }),
expensesTypeId: z expenseCategoryId: z
.string() .string()
.min(1, { message: "Expense type is required" }), .min(1, { message: "Expense type is required" }),
paymentModeId: z.string().min(1, { message: "Payment mode is required" }), paymentModeId: z.string().min(1, { message: "Payment mode is required" }),
paidById: z.string().min(1, { message: "Employee name is required" }), paidById: z.string().min(1, { message: "Employee name is required" }),
transactionDate: z transactionDate: z.string().min(1, { message: "Date is required" }),
.string()
.min(1, { message: "Date is required" })
,
transactionId: z.string().optional(), transactionId: z.string().optional(),
description: z.string().min(1, { message: "Description is required" }), description: z.string().min(1, { message: "Description is required" }),
location: z.string().min(1, { message: "Location is required" }), location: z.string().min(1, { message: "Location is required" }),
supplerName: z.string().min(1, { message: "Supplier name 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 amount: z.coerce
.number({ .number({
invalid_type_error: "Amount is required and must be a 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" }), .nonempty({ message: "At least one file attachment is required" }),
}) })
.refine( .refine(
(data) => { (data) => {
@ -69,8 +70,13 @@ export const ExpenseSchema = (expenseTypes) => {
} }
) )
.superRefine((data, ctx) => { .superRefine((data, ctx) => {
const expenseType = expenseTypes.find((et) => et.id === data.expensesTypeId); const ExpenseCategory = ExpenseCategories.find(
if (expenseType?.noOfPersonsRequired && (!data.noOfPersons || data.noOfPersons < 1)) { (et) => et.id === data.expenseCategoryId
);
if (
ExpenseCategory?.noOfPersonsRequired &&
(!data.noOfPersons || data.noOfPersons < 1)
) {
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
message: "No. of Persons is required and must be at least 1", message: "No. of Persons is required and must be at least 1",
@ -82,7 +88,7 @@ export const ExpenseSchema = (expenseTypes) => {
export const defaultExpense = { export const defaultExpense = {
projectId: "", projectId: "",
expensesTypeId: "", expenseCategoryId: "",
paymentModeId: "", paymentModeId: "",
paidById: "", paidById: "",
transactionDate: "", transactionDate: "",
@ -92,12 +98,15 @@ export const defaultExpense = {
supplerName: "", supplerName: "",
amount: "", amount: "",
noOfPersons: "", noOfPersons: "",
gstNumber:"", gstNumber: "",
currencyId: DEFAULT_CURRENCY,
billAttachments: [], billAttachments: [],
}; };
export const ExpenseActionScheam = (
export const ExpenseActionScheam = (isReimbursement = false) => { isReimbursement = false,
transactionDate
) => {
return z return z
.object({ .object({
comment: z.string().min(1, { message: "Please leave comment" }), comment: z.string().min(1, { message: "Please leave comment" }),
@ -105,6 +114,9 @@ export const ExpenseActionScheam = (isReimbursement = false) => {
reimburseTransactionId: z.string().nullable().optional(), reimburseTransactionId: z.string().nullable().optional(),
reimburseDate: z.string().nullable().optional(), reimburseDate: z.string().nullable().optional(),
reimburseById: 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) => { .superRefine((data, ctx) => {
if (isReimbursement) { if (isReimbursement) {
@ -122,6 +134,7 @@ export const ExpenseActionScheam = (isReimbursement = false) => {
message: "Reimburse Date is required", message: "Reimburse Date is required",
}); });
} }
if (!data.reimburseById) { if (!data.reimburseById) {
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
@ -129,26 +142,42 @@ export const ExpenseActionScheam = (isReimbursement = false) => {
message: "Reimburse By is required", 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: "", comment: "",
statusId: "", statusId: "",
reimburseTransactionId: null, reimburseTransactionId: null,
reimburseDate: null, reimburseDate: null,
reimburseById: null, reimburseById: null,
tdsPercentage: null,
baseAmount:null,
taxAmount: null,
}; };
export const SearchSchema = z.object({ export const SearchSchema = z.object({
projectIds: z.array(z.string()).optional(), projectIds: z.array(z.string()).optional(),
statusIds: z.array(z.string()).optional(), statusIds: z.array(z.string()).optional(),
createdByIds: z.array(z.string()).optional(), createdByIds: z.array(z.string()).optional(),
paidById: z.array(z.string()).optional(), paidById: z.array(z.string()).optional(),
ExpenseCategoryIds: z.array(z.string()).optional(),
startDate: z.string().optional(), startDate: z.string().optional(),
endDate: z.string().optional(), endDate: z.string().optional(),
isTransactionDate: z.boolean().default(true), isTransactionDate: z.boolean().default(true),
@ -159,8 +188,8 @@ export const defaultFilter = {
statusIds: [], statusIds: [],
createdByIds: [], createdByIds: [],
paidById: [], paidById: [],
ExpenseCategoryIds: [],
isTransactionDate: true, isTransactionDate: true,
startDate: null, startDate: null,
endDate: null, endDate: null,
}; };

View File

@ -1,10 +1,11 @@
import { useState,useMemo } from "react"; import { useState, useMemo } from "react";
import Avatar from "../common/Avatar"; import Avatar from "../common/Avatar";
import { formatUTCToLocalTime } from "../../utils/dateUtils"; import { formatUTCToLocalTime } from "../../utils/dateUtils";
import Timeline from "../common/TimeLine";
import moment from "moment";
import { getColorNameFromHex } from "../../utils/appUtils";
const ExpenseStatusLogs = ({ data }) => { const ExpenseStatusLogs = ({ data }) => {
const [visibleCount, setVisibleCount] = useState(4);
const sortedLogs = useMemo(() => { const sortedLogs = useMemo(() => {
if (!data?.expenseLogs) return []; if (!data?.expenseLogs) return [];
@ -13,56 +14,35 @@ const ExpenseStatusLogs = ({ data }) => {
); );
}, [data?.expenseLogs]); }, [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 = () => { const handleShowMore = () => {
setVisibleCount((prev) => prev + 4); setVisibleCount((prev) => prev + 4);
}; };
return ( return (
<> <div className="page-min-h overflow-auto py-1">
<div className="row g-2"> <Timeline items={timelineData} />
{logsToShow.map((log) => (
<div key={log.id} className="col-12 d-flex align-items-start mb-1">
<Avatar
size="xs"
firstName={log.updatedBy.firstName}
lastName={log.updatedBy.lastName}
/>
<div className="flex-grow-1">
<div className="text-start">
<div className="flex">
<span>{`${log.updatedBy.firstName} ${log.updatedBy.lastName}`}</span>
<small className="text-secondary text-tiny ms-2">
<em>{log.action}</em>
</small>
<span className="text-tiny text-secondary d-block" >
{formatUTCToLocalTime(log.updateAt,true)}
</span>
</div> </div>
<div className="d-flex align-items-center text-muted small mt-1">
<span>{log.comment}</span>
</div>
</div>
</div>
</div>
))}
</div>
{sortedLogs.length > visibleCount && (
<div className="text-center my-1">
<button
className="btn btn-xs btn-outline-primary"
onClick={handleShowMore}
>
Show More
</button>
</div>
)}
</>
); );
}; };
export default ExpenseStatusLogs; export default ExpenseStatusLogs;

View File

@ -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 (
<div className="d-flex flex-wrap gap-2 my-1">
{files
.filter((file) => {
if (expenseToEdit) {
return file.isActive;
}
return true;
})
.map((file, idx) => (
<div className="col-12 col-sm-6 col-md-4 mb-2" key={idx}>
<div className="d-flex align-items-center justify-content-between bg-white border rounded p-1">
{/* File icon and info */}
<div className="d-flex align-items-center flex-grow-1 gap-2 overflow-hidden">
<i
className={`bx ${getIconByFileType(
file?.contentType
)} fs-3 text-primary`}
style={{ minWidth: "30px" }}
></i>
<div className="d-flex flex-column text-truncate">
<span className="fw-semibold small text-truncate">
{file.fileName}
</span>
<span className="text-body-secondary small">
{file.fileSize ? formatFileSize(file.fileSize) : ""}
</span>
</div>
</div>
{/* Delete icon */}
<Tooltip text="Remove file">
<i
className="bx bx-sm bx-trash text-danger fs-4 cursor-pointer ms-2"
role="button"
onClick={(e) => {
e.preventDefault();
removeFile(expenseToEdit ? file.documentId : idx);
}}
></i>
</Tooltip>
</div>
</div>
))}
</div>
);
};
export default Filelist;
export const FilelistView = ({ files, viewFile }) => {
return (
<div className="d-flex flex-wrap gap-2 mt-2">
{files?.map((file, idx) => (
<div className=" bg-white " key={idx}>
<div className="row align-items-center">
{/* File icon and info */}
<div className="col-12 d-flex align-items-center gap-2">
<i
className={`bx ${getIconByFileType(file?.fileName)} fs-3`}
></i>
<div
className="d-flex flex-column text-truncate"
onClick={(e) => {
e.preventDefault();
viewFile({
IsOpen: true,
Image: file.preSignedUrl,
});
}}
>
<span className="fw-medium small text-truncate">
{file.fileName}
</span>
<span className="text-body-secondary small">
<Tooltip text={"Click on file"}>
{" "}
{file.fileSize ? formatFileSize(file.fileSize) : ""}
</Tooltip>
</span>
</div>
</div>
</div>
</div>
))}
</div>
);
};

View File

@ -7,8 +7,8 @@ import { useProjectName } from "../../hooks/useProjects";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { changeMaster } from "../../slices/localVariablesSlice"; import { changeMaster } from "../../slices/localVariablesSlice";
import useMaster, { import useMaster, {
useExpenseCategory,
useExpenseStatus, useExpenseStatus,
useExpenseType,
usePaymentMode, usePaymentMode,
} from "../../hooks/masterHook/useMaster"; } from "../../hooks/masterHook/useMaster";
import { import {
@ -39,11 +39,11 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
const [ExpenseType, setExpenseType] = useState(); const [ExpenseType, setExpenseType] = useState();
const dispatch = useDispatch(); const dispatch = useDispatch();
const { const {
ExpenseTypes, expenseCategories,
loading: ExpenseLoading, loading: ExpenseLoading,
error: ExpenseError, error: ExpenseError,
} = useExpenseType(); } = useExpenseCategory();
const schema = ExpenseSchema(ExpenseTypes); const schema = ExpenseSchema(expenseCategories);
const { const {
register, register,
handleSubmit, handleSubmit,
@ -146,7 +146,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
if (expenseToEdit && data) { if (expenseToEdit && data) {
reset({ reset({
projectId: data.project.id || "", projectId: data.project.id || "",
expensesTypeId: data.expensesType.id || "", expenseCategoryId: data?.expenseCategory?.id || "",
paymentModeId: data.paymentMode.id || "", paymentModeId: data.paymentMode.id || "",
paidById: data.paidBy.id || "", paidById: data.paidBy.id || "",
transactionDate: data.transactionDate?.slice(0, 10) || "", transactionDate: data.transactionDate?.slice(0, 10) || "",
@ -192,11 +192,11 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
CreateExpense(payload); CreateExpense(payload);
} }
}; };
const ExpenseTypeId = watch("expensesTypeId"); const expenseCategoryId = watch("expenseCategoryId");
useEffect(() => { useEffect(() => {
setExpenseType(ExpenseTypes?.find((type) => type.id === ExpenseTypeId)); setExpenseType(expenseCategories?.find((type) => type.id === expenseCategoryId));
}, [ExpenseTypeId]); }, [expenseCategoryId]);
const handleClose = () => { const handleClose = () => {
reset(); reset();
@ -237,13 +237,13 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
</div> </div>
<div className="col-md-6"> <div className="col-md-6">
<Label htmlFor="expensesTypeId" className="form-label" required> <Label htmlFor="expenseCategoryId" className="form-label" required>
Expense Type Expense Type
</Label> </Label>
<select <select
className="form-select form-select-sm" className="form-select form-select-sm"
id="expensesTypeId" id="expenseCategoryId"
{...register("expensesTypeId")} {...register("expenseCategoryId")}
> >
<option value="" disabled> <option value="" disabled>
Select Type Select Type
@ -251,16 +251,16 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
{ExpenseLoading ? ( {ExpenseLoading ? (
<option disabled>Loading...</option> <option disabled>Loading...</option>
) : ( ) : (
ExpenseTypes?.map((expense) => ( expenseCategories?.map((expense) => (
<option key={expense.id} value={expense.id}> <option key={expense.id} value={expense.id}>
{expense.name} {expense.name}
</option> </option>
)) ))
)} )}
</select> </select>
{errors.expensesTypeId && ( {errors.expenseCategoryId && (
<small className="danger-text"> <small className="danger-text">
{errors.expensesTypeId.message} {errors.expenseCategoryId.message}
</small> </small>
)} )}
</div> </div>

View File

@ -1,137 +1,54 @@
import { useState, useRef ,useEffect} from "react"; import { useState } from "react";
const PreviewDocument = ({ imageUrl }) => { const PreviewDocument = ({ imageUrl }) => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [rotation, setRotation] = useState(0); 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 ( return (
<> <>
<div className="d-flex justify-content-start align-items-center gap-3 mb-2 px-3 py-2 px-md-0 py-md-0"> <div className="d-flex justify-content-start">
<i <i
className="bx bx-rotate-right fs-4 cursor-pointer" className="bx bx-rotate-right cursor-pointer"
title="Rotate Right"
onClick={() => setRotation((prev) => prev + 90)} onClick={() => setRotation((prev) => prev + 90)}
></i> ></i>
<i
className="bx bx-zoom-in fs-4 cursor-pointer"
title="Zoom In"
onClick={handleZoomIn}
></i>
<i
className="bx bx-zoom-out fs-4 cursor-pointer"
title="Zoom Out"
onClick={handleZoomOut}
></i>
<i
className="bx bx-reset fs-4 cursor-pointer"
title="Reset"
onClick={handleReset}
></i>
</div> </div>
<div <div
ref={containerRef} className="position-relative d-flex flex-column justify-content-center align-items-center"
onMouseDown={handleMouseDown} style={{ minHeight: "80vh" }}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseLeave}
className="d-flex justify-content-center align-items-center overflow-hidden border rounded "
style={{
width: "100%",
height: "80vh",
background: "#f8f9fa",
cursor: zoom > 1 ? (isDragging ? "grabbing" : "grab") : "default",
userSelect: "none",
position: "relative",
}}
> >
{loading && ( {loading && (
<div className="text-secondary text-center position-absolute"> <div className="text-secondary text-center mb-2">Loading...</div>
Loading...
</div>
)} )}
<div className="mb-3 d-flex justify-content-center align-items-center">
<img <img
src={imageUrl} src={imageUrl}
alt="Preview" alt="Full View"
onLoad={() => setLoading(false)} className="img-fluid"
style={{ style={{
transform: `translate(${position.x}px, ${position.y}px) rotate(${rotation}deg) scale(${zoom})`, maxHeight: "80vh",
transition: isDragging ? "none" : "transform 0.3s ease",
objectFit: "contain", objectFit: "contain",
maxWidth: "100%",
maxHeight: "100%",
display: loading ? "none" : "block", display: loading ? "none" : "block",
pointerEvents: "none", transform: `rotate(${rotation}deg)`,
transition: "transform 0.3s ease",
}} }}
onLoad={() => setLoading(false)}
/> />
</div> </div>
{/* <div className="d-flex justify-content-center gap-2 mt-2"> <div className="position-absolute bottom-0 start-0 justify-content-center gap-2">
<button <button
className="btn btn-sm btn-outline-secondary" className="btn btn-outline-secondary"
onClick={handleReset} onClick={() => setRotation(0)}
title="Reset View" title="Reset Rotation"
> >
<i className="bx bx-reset"></i> Reset View <i className="bx bx-reset"></i> Reset
</button> </button>
</div> */} </div>
</div>
</> </>
); );
}; };
export default PreviewDocument; export default PreviewDocument;

View File

@ -10,6 +10,8 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { defaultActionValues, ExpenseActionScheam } from "./ExpenseSchema"; import { defaultActionValues, ExpenseActionScheam } from "./ExpenseSchema";
import { useExpenseContext } from "../../pages/Expense/ExpensePage"; import { useExpenseContext } from "../../pages/Expense/ExpensePage";
import { import {
formatCurrency,
formatFigure,
getColorNameFromHex, getColorNameFromHex,
getIconByFileType, getIconByFileType,
localToUtc, localToUtc,
@ -42,7 +44,7 @@ const ViewExpense = ({ ExpenseId }) => {
const IsReview = useHasUserPermission(REVIEW_EXPENSE); const IsReview = useHasUserPermission(REVIEW_EXPENSE);
const [imageLoaded, setImageLoaded] = useState({}); const [imageLoaded, setImageLoaded] = useState({});
const { setDocumentView } = useExpenseContext(); const { setDocumentView } = useExpenseContext();
const ActionSchema = ExpenseActionScheam(IsPaymentProcess) ?? z.object({}); const ActionSchema = ExpenseActionScheam(IsPaymentProcess,data?.createdAt) ?? z.object({});
const navigate = useNavigate(); const navigate = useNavigate();
const { const {
register, register,
@ -95,7 +97,7 @@ const ViewExpense = ({ ExpenseId }) => {
const onSubmit = (formData) => { const onSubmit = (formData) => {
const Payload = { const Payload = {
...formData, ...formData,
reimburseDate: localToUtc(formData.reimburseDate), reimburseDate:localToUtc(formData.reimburseDate),
expenseId: ExpenseId, expenseId: ExpenseId,
comment: formData.comment, comment: formData.comment,
}; };
@ -107,17 +109,28 @@ const ViewExpense = ({ ExpenseId }) => {
const handleImageLoad = (id) => { const handleImageLoad = (id) => {
setImageLoaded((prev) => ({ ...prev, [id]: true })); setImageLoaded((prev) => ({ ...prev, [id]: true }));
}; };
console.log(errors)
return ( return (
<form className="container px-3" onSubmit={handleSubmit(onSubmit)}> <form className="container px-3" onSubmit={handleSubmit(onSubmit)}>
<div className="row mb-3"> <div className="col-12 mb-1">
<div className="col-12 mb-3"> <h5 className="fw-semibold m-0">Expense Details</h5>
<h5 className="fw-semibold">Expense Details</h5>
<hr />
</div> </div>
<div className="text-start mb-2">
<div className="text-muted">{data?.description}</div> <div className="row mb-1 ">
<div className="col-12 col-lg-7 col-xl-7 mb-3">
<div className="row">
<div className="col-12 d-flex justify-content-between text-start fw-semibold my-2">
<span>{data?.expenseUId}</span>
<span
className={`badge bg-label-${
getColorNameFromHex(data?.status?.color) || "secondary"
}`}
t
>
{data?.status?.name}
</span>
</div> </div>
{/* Row 1 */} {/* Row 1 */}
<div className="col-md-6 mb-3"> <div className="col-md-6 mb-3">
<div className="d-flex"> <div className="d-flex">
@ -132,15 +145,16 @@ const ViewExpense = ({ ExpenseId }) => {
</div> </div>
</div> </div>
</div> </div>
<div className="col-md-6 mb-3"> <div className="col-md-6 mb-3">
<div className="d-flex"> <div className="d-flex">
<label <label
className="form-label me-2 mb-0 fw-semibold text-start" className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }} style={{ minWidth: "130px" }}
> >
Expense Type : Expense Category :
</label> </label>
<div className="text-muted">{data?.expensesType?.name}</div> <div className="text-muted">{data?.expenseCategory?.name}</div>
</div> </div>
</div> </div>
@ -156,6 +170,7 @@ const ViewExpense = ({ ExpenseId }) => {
<div className="text-muted">{data?.supplerName}</div> <div className="text-muted">{data?.supplerName}</div>
</div> </div>
</div> </div>
<div className="col-md-6 mb-3"> <div className="col-md-6 mb-3">
<div className="d-flex"> <div className="d-flex">
<label <label
@ -164,7 +179,13 @@ const ViewExpense = ({ ExpenseId }) => {
> >
Amount : Amount :
</label> </label>
<div className="text-muted"> {data.amount}</div> <div className="text-muted">
{" "}
{formatFigure(data?.amount, {
type: "currency",
currency: data?.currency?.currencyCode ?? "INR",
})}
</div>
</div> </div>
</div> </div>
@ -180,6 +201,7 @@ const ViewExpense = ({ ExpenseId }) => {
<div className="text-muted">{data?.paymentMode?.name}</div> <div className="text-muted">{data?.paymentMode?.name}</div>
</div> </div>
</div> </div>
{data?.gstNumber && ( {data?.gstNumber && (
<div className="col-md-6 mb-3"> <div className="col-md-6 mb-3">
<div className="d-flex"> <div className="d-flex">
@ -194,24 +216,6 @@ const ViewExpense = ({ ExpenseId }) => {
</div> </div>
)} )}
{/* Row 4 */}
<div className="col-md-6 mb-3">
<div className="d-flex">
<label
className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }}
>
Status :
</label>
<span
className={`badge bg-label-${
getColorNameFromHex(data?.status?.color) || "secondary"
}`}
>
{data?.status?.name}
</span>
</div>
</div>
<div className="col-md-6 mb-3"> <div className="col-md-6 mb-3">
<div className="d-flex"> <div className="d-flex">
<label <label
@ -220,10 +224,13 @@ const ViewExpense = ({ ExpenseId }) => {
> >
Pre-Approved : Pre-Approved :
</label> </label>
<div className="text-muted">{data.preApproved ? "Yes" : "No"}</div> <div className="text-muted">
{data.preApproved ? "Yes" : "No"}
</div>
</div> </div>
</div> </div>
{/* Row 5 */}
<div className="col-md-6 mb-3"> <div className="col-md-6 mb-3">
<div className="d-flex"> <div className="d-flex">
<label <label
@ -235,6 +242,7 @@ const ViewExpense = ({ ExpenseId }) => {
<div className="text-muted">{data?.project?.name}</div> <div className="text-muted">{data?.project?.name}</div>
</div> </div>
</div> </div>
<div className="col-md-6 mb-3"> <div className="col-md-6 mb-3">
<div className="d-flex"> <div className="d-flex">
<label <label
@ -249,9 +257,9 @@ const ViewExpense = ({ ExpenseId }) => {
</div> </div>
</div> </div>
{/* Row 6 */} {/* Created & Paid By */}
{data.createdBy && ( {data.createdBy && (
<div className="col-md-6 text-start"> <div className="col-md-6 text-start mb-3">
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<label <label
className="form-label me-2 mb-0 fw-semibold" className="form-label me-2 mb-0 fw-semibold"
@ -259,10 +267,9 @@ const ViewExpense = ({ ExpenseId }) => {
> >
Created By : Created By :
</label> </label>
<div className="d-flex align-items-center">
<Avatar <Avatar
size="xs" size="xs"
classAvatar="m-0" classAvatar="m-0 me-1"
firstName={data.createdBy?.firstName} firstName={data.createdBy?.firstName}
lastName={data.createdBy?.lastName} lastName={data.createdBy?.lastName}
/> />
@ -273,20 +280,19 @@ const ViewExpense = ({ ExpenseId }) => {
</span> </span>
</div> </div>
</div> </div>
</div>
)} )}
<div className="col-md-6 text-start">
<div className="col-md-6 text-start mb-3">
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<label <label
className="form-label me-2 mb-0 fw-semibold" className="form-label me-2 mb-0 fw-semibold"
style={{ minWidth: "130px" }} style={{ minWidth: "130px" }}
> >
Paid By: Paid By :
</label> </label>
<div className="d-flex align-items-center ">
<Avatar <Avatar
size="xs" size="xs"
classAvatar="m-0" classAvatar="m-0 me-1"
firstName={data.paidBy?.firstName} firstName={data.paidBy?.firstName}
lastName={data.paidBy?.lastName} lastName={data.paidBy?.lastName}
/> />
@ -297,32 +303,31 @@ const ViewExpense = ({ ExpenseId }) => {
</span> </span>
</div> </div>
</div> </div>
</div>
{/* Description */}
<div className="col-12 text-start mb-2">
<label className="fw-semibold form-label">Description : </label>
<div className="text-muted">{data?.description}</div>
</div> </div>
<div className="col-12 text-start"> {/* Attachments */}
<label className="form-label me-2 mb-2 fw-semibold">Attachment :</label> <div className="col-12 text-start mb-2">
<label className="form-label me-2 mb-2 fw-semibold">
Attachment :
</label>
<div className="d-flex flex-wrap gap-2"> <div className="d-flex flex-wrap gap-2">
{data?.documents?.map((doc) => { {data?.documents?.map((doc) => {
const isImage = doc.contentType?.startsWith("image"); const isImage = doc.contentType?.includes("image");
return ( return (
<div <div
key={doc.documentId} key={doc.documentId}
className="border rounded hover-scale p-2 d-flex flex-column align-items-center" className="d-flex align-items-center cusor-pointer"
style={{
width: "80px",
cursor: "pointer",
}}
onClick={() => { onClick={() => {
if (isImage) { if (isImage) {
setDocumentView({ setDocumentView({
IsOpen: true, IsOpen: true,
Image: doc.preSignedUrl, Image: doc.preSignedUrl,
}); });
} else {
window.open(doc.preSignedUrl, "_blank");
} }
}} }}
> >
@ -338,7 +343,7 @@ const ViewExpense = ({ ExpenseId }) => {
</small> </small>
</div> </div>
); );
}) ?? "No Attachment"} })}
</div> </div>
</div> </div>
@ -366,8 +371,12 @@ const ViewExpense = ({ ExpenseId }) => {
<Avatar <Avatar
size="xs" size="xs"
classAvatar="m-0 me-1" classAvatar="m-0 me-1"
firstName={data?.expensesReimburse?.reimburseBy?.firstName} firstName={
lastName={data?.expensesReimburse?.reimburseBy?.lastName} data?.expensesReimburse?.reimburseBy?.firstName
}
lastName={
data?.expensesReimburse?.reimburseBy?.lastName
}
/> />
<span className="text-muted"> <span className="text-muted">
{`${data?.expensesReimburse?.reimburseBy?.firstName} ${data?.expensesReimburse?.reimburseBy?.lastName}`.trim()} {`${data?.expensesReimburse?.reimburseBy?.firstName} ${data?.expensesReimburse?.reimburseBy?.lastName}`.trim()}
@ -377,14 +386,14 @@ const ViewExpense = ({ ExpenseId }) => {
)} )}
</div> </div>
)} )}
<hr className="divider my-1 border-2 divider-primary my-2" /> {/* <hr className="divider my-1 border-2 divider-primary my-2" /> */}
{Array.isArray(data?.nextStatus) && data.nextStatus.length > 0 && ( {Array.isArray(data?.nextStatus) && data.nextStatus.length > 0 && (
<> <>
{IsPaymentProcess && nextStatusWithPermission?.length > 0 && ( {IsPaymentProcess && nextStatusWithPermission?.length > 0 && (
<div className="row"> <div className="row ">
<div className="col-12 col-md-6 text-start"> <div className="col-12 col-md-6 text-start">
<label className="form-label">Transaction Id </label> <Label className="form-label" required>Transaction Id </Label>
<input <input
type="text" type="text"
className="form-control form-control-sm" className="form-control form-control-sm"
@ -396,9 +405,9 @@ const ViewExpense = ({ ExpenseId }) => {
</small> </small>
)} )}
</div> </div>
<div className="col-12 col-md-6 text-start"> <div className="col-12 col-md-6 text-start mb-1">
<label className="form-label">Transaction Date </label> <Label className="form-label" required>Transaction Date </Label>
<DatePicker <DatePicker className="w-100"
name="reimburseDate" name="reimburseDate"
control={control} control={control}
minDate={data?.transactionDate} minDate={data?.transactionDate}
@ -410,18 +419,66 @@ const ViewExpense = ({ ExpenseId }) => {
</small> </small>
)} )}
</div> </div>
<div className="col-12 col-md-6 text-start"> <div className="col-12 col-md-6 text-start mb-1">
<label className="form-label">Reimburse By </label> <Label className="form-label" required>
Reimburse By{" "}
</Label>
<EmployeeSearchInput <EmployeeSearchInput
control={control} control={control}
name="reimburseById" name="reimburseById"
projectId={null} projectId={null}
/> />
</div> </div>
<div className="col-12 col-md-6 text-start">
<Label className="form-label" >
TDS Percentage
</Label>
<input
type="text"
className="form-control form-control-sm"
{...register("tdsPercentage")}
/>
{errors.tdsPercentage && (
<small className="danger-text">
{errors.tdsPercentage.message}
</small>
)}
</div>
<div className="col-12 col-md-6 text-start">
<Label className="form-label" required>
Base Amount
</Label>
<input
type="text"
className="form-control form-control-sm"
{...register("baseAmount")}
/>
{errors.baseAmount && (
<small className="danger-text">
{errors.baseAmount.message}
</small>
)}
</div>
<div className="col-12 col-md-6 text-start">
<Label className="form-label" required>
Tax Amount
</Label>
<input
type="text"
className="form-control form-control-sm"
{...register("taxAmount")}
/>
{errors.taxAmount && (
<small className="danger-text">
{errors.taxAmount.message}
</small>
)}
</div>
</div> </div>
)} )}
<div className="col-12 mb-3 text-start"> <div className="col-12 mb-3 text-start mt-1">
{((nextStatusWithPermission.length > 0 && !IsRejectedExpense) || {((nextStatusWithPermission.length > 0 &&
!IsRejectedExpense) ||
(IsRejectedExpense && isCreatedBy)) && ( (IsRejectedExpense && isCreatedBy)) && (
<> <>
<Label className="form-label me-2 mb-0" required> <Label className="form-label me-2 mb-0" required>
@ -465,8 +522,17 @@ const ViewExpense = ({ ExpenseId }) => {
</div> </div>
</> </>
)} )}
</div>
</div>
<div className="col-12 col-lg-5 col-xl-5">
<div className="d-flex align-items-center text-secondary mb-4">
<i className="bx bx-time-five me-2"></i>{" "}
<p className=" m-0">TimeLine</p>
</div>
<ExpenseStatusLogs data={data} /> <ExpenseStatusLogs data={data} />
</div>
</div>
</form> </form>
); );
}; };

View File

@ -0,0 +1,104 @@
import React from "react";
import Avatar from "./Avatar";
import Tooltip from "./Tooltip";
import { formatUTCToLocalTime } from "../../utils/dateUtils";
import moment from "moment";
const Timeline = ({ items = [], transparent = true }) => {
if(items.length === 0){
return (
<div className="d-flex justify-content-center align-item-center page-min-h">
<p>Not Action yet</p>
</div>
)
}
return (
<ul
className={`timeline ${
transparent ? "timeline-transparent text-start" : ""
}`}
>
{items.map((item) => (
<li
key={item.id}
className={`timeline-item ${
transparent ? "timeline-item-transparent" : ""
}`}
>
<span
className={`timeline-point timeline-point-${
item.color || "primary"
}`}
></span>
<div className="timeline-event">
<div className="timeline-header mb-1 d-flex justify-content-between">
<h6 className="mb-0 text-body">{item.title}</h6>
<small className="text-body-secondary"><Tooltip text={formatUTCToLocalTime(item.timeAgo,true)}>{moment.utc(item.timeAgo).local().fromNow()}</Tooltip></small>
</div>
{item.description && <p className="mb-1">{item.description}</p>}
{item.attachments && item.attachments.length > 0 && (
<div className="d-flex align-items-center mb-2">
{item.attachments.map((att, i) => (
<div
key={i}
className="badge bg-lighter rounded d-flex align-items-center gap-2 p-2"
>
{att.icon && (
<img
src={att.icon}
alt="file"
width="15"
className="me-2"
/>
)}
<span className="h6 mb-0">{att.name}</span>
</div>
))}
</div>
)}
{item.users && item.users.length > 0 && (
<div className="d-flex flex-wrap align-items-center ">
<ul className="list-unstyled users-list d-flex align-items-center avatar-group m-0">
{item.users.map((user, i) => (
<li key={i} className="avatar me-1" title={user.name}>
{user.avatar ? (
<img
src={user.avatar}
alt={user.name}
className="rounded-circle"
width="32"
height="32"
/>
) : (
<Avatar
firstName={user.firstName}
lastName={user.lastName}
/>
)}
</li>
))}
</ul>
{item.users?.length === 1 && (
<div className="m-0">
<p className="mb-0 small fw-medium">{`${item.users[0].firstName} ${item.users[0].lastName}`}</p>
<small>{item.users[0].role}</small>
</div>
)}
</div>
)}
<div className="d-flex flex-wrap ms-10">{item.userComment && <p className="mb-2 ">{item.userComment}</p>}</div>
</div>
</li>
))}
</ul>
);
};
export default Timeline;

View File

@ -0,0 +1,24 @@
import { useEffect } from "react";
const Tooltip = ({ text, placement = "top", children }) => {
useEffect(() => {
const el = document.querySelector(`[data-tooltip-id="${text}"]`);
if (el) {
new window.bootstrap.Tooltip(el);
}
}, [text]);
return (
<span
data-bs-toggle="tooltip"
data-bs-placement={placement}
title={text}
data-tooltip-id={text}
style={{ cursor: "pointer", display: "inline-flex", alignItems: "center" }}
>
{children}
</span>
);
};
export default Tooltip;

View File

@ -2,10 +2,7 @@ import React, { useEffect } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { z } from "zod"; import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { import { useCreateExpenseCategory, useUpdateExpenseCategory } from "../../hooks/masterHook/useMaster";
useCreateExpenseType,
useUpdateExpenseType,
} from "../../hooks/masterHook/useMaster";
import Label from "../common/Label"; import Label from "../common/Label";
const ExpnseSchema = z.object({ const ExpnseSchema = z.object({
@ -14,7 +11,7 @@ const ExpnseSchema = z.object({
description: z.string().min(1, { message: "Description is required" }), description: z.string().min(1, { message: "Description is required" }),
}); });
const ManageExpenseType = ({ data = null, onClose }) => { const ManageExpenseCategory = ({ data = null, onClose }) => {
const { const {
register, register,
handleSubmit, handleSubmit,
@ -24,21 +21,21 @@ const ManageExpenseType = ({ data = null, onClose }) => {
resolver: zodResolver(ExpnseSchema), resolver: zodResolver(ExpnseSchema),
defaultValues: { name: "", noOfPersonsRequired: false, description: "" }, defaultValues: { name: "", noOfPersonsRequired: false, description: "" },
}); });
const { mutate: UpdateExpenseType, isPending:isPendingUpdate } = useUpdateExpenseType( const { mutate: UpdateExpenseCategory, isPending:isPendingUpdate } = useUpdateExpenseCategory(
() => onClose?.() () => onClose?.()
); );
const { mutate: CreateExpenseType, isPending } = useCreateExpenseType(() => const { mutate: CreateExpenseCategory, isPending } = useCreateExpenseCategory(() =>
onClose?.() onClose?.()
); );
const onSubmit = (payload) => { const onSubmit = (payload) => {
if (data) { if (data) {
UpdateExpenseType({ UpdateExpenseCategory({
id: data.id, id: data.id,
payload: { ...payload, id: data.id }, payload: { ...payload, id: data.id },
}); });
} else { } else {
CreateExpenseType(payload); CreateExpenseCategory(payload);
} }
}; };
@ -112,4 +109,4 @@ const ManageExpenseType = ({ data = null, onClose }) => {
); );
}; };
export default ManageExpenseType; export default ManageExpenseCategory;

View File

@ -9,7 +9,6 @@ import CreateCategory from "./CreateContactCategory";
import CreateContactTag from "./CreateContactTag"; import CreateContactTag from "./CreateContactTag";
import EditContactCategory from "./EditContactCategory"; import EditContactCategory from "./EditContactCategory";
import EditContactTag from "./EditContactTag"; import EditContactTag from "./EditContactTag";
import ManageExpenseType from "./ManageExpenseType";
import ManagePaymentMode from "./ManagePaymentMode"; import ManagePaymentMode from "./ManagePaymentMode";
import ManageExpenseStatus from "./ManageExpenseStatus"; import ManageExpenseStatus from "./ManageExpenseStatus";
import ManageDocumentCategory from "./ManageDocumentCategory"; import ManageDocumentCategory from "./ManageDocumentCategory";
@ -17,6 +16,7 @@ import ManageDocumentType from "./ManageDocumentType";
import ManageServices from "./Services/ManageServices"; import ManageServices from "./Services/ManageServices";
import ServiceGroups from "./Services/ServicesGroups"; import ServiceGroups from "./Services/ServicesGroups";
import ManagePaymentHead from "./paymentAdjustmentHead/ManagePaymentHead"; import ManagePaymentHead from "./paymentAdjustmentHead/ManagePaymentHead";
import ManageExpenseCategory from "./ManageExpenseCategory";
const MasterModal = ({ modaldata, closeModal }) => { const MasterModal = ({ modaldata, closeModal }) => {
if (!modaldata?.modalType || modaldata.modalType === "delete") { if (!modaldata?.modalType || modaldata.modalType === "delete") {
@ -42,8 +42,8 @@ const MasterModal = ({ modaldata, closeModal }) => {
), ),
"Contact Tag": <CreateContactTag data={item} onClose={closeModal} />, "Contact Tag": <CreateContactTag data={item} onClose={closeModal} />,
"Edit-Contact Tag": <EditContactTag data={item} onClose={closeModal} />, "Edit-Contact Tag": <EditContactTag data={item} onClose={closeModal} />,
"Expense Type": <ManageExpenseType onClose={closeModal} />, "Expense Category": <ManageExpenseCategory onClose={closeModal} />,
"Edit-Expense Type": <ManageExpenseType data={item} onClose={closeModal} />, "Edit-Expense Category": <ManageExpenseCategory data={item} onClose={closeModal} />,
"Payment Mode": <ManagePaymentMode onClose={closeModal} />, "Payment Mode": <ManagePaymentMode onClose={closeModal} />,
"Edit-Payment Mode": <ManagePaymentMode data={item} onClose={closeModal} />, "Edit-Payment Mode": <ManagePaymentMode data={item} onClose={closeModal} />,
"Expense Status": <ManageExpenseStatus onClose={closeModal} />, "Expense Status": <ManageExpenseStatus onClose={closeModal} />,

View File

@ -150,15 +150,15 @@ export const useContactTags = () => {
return { contactTags, loading, error }; return { contactTags, loading, error };
}; };
export const useExpenseType = () => { export const useExpenseCategory = () => {
const { const {
data: ExpenseTypes = [], data: expenseCategories = [],
isLoading: loading, isLoading: loading,
error, error,
} = useQuery({ } = useQuery({
queryKey: ["Expense Type"], queryKey: ["Expense Category"],
queryFn: async () => { queryFn: async () => {
const res = await MasterRespository.getExpenseType(); const res = await MasterRespository.getExpenseCategories();
return res.data; return res.data;
}, },
onError: (error) => { onError: (error) => {
@ -275,8 +275,8 @@ export const useOrganizationType = () => {
queryFn: async () => await MasterRespository.getOrganizationType(), queryFn: async () => await MasterRespository.getOrganizationType(),
}); });
}; };
// ===Application Masters Query=================================================
//#region ==Get Masters==
const fetchMasterData = async (masterType) => { const fetchMasterData = async (masterType) => {
switch (masterType) { switch (masterType) {
case "Application Role": case "Application Role":
@ -293,8 +293,8 @@ const fetchMasterData = async (masterType) => {
return (await MasterRespository.getContactCategory()).data; return (await MasterRespository.getContactCategory()).data;
case "Contact Tag": case "Contact Tag":
return (await MasterRespository.getContactTag()).data; return (await MasterRespository.getContactTag()).data;
case "Expense Type": case "Expense Category":
return (await MasterRespository.getExpenseType()).data; return (await MasterRespository.getExpenseCategories()).data;
case "Payment Mode": case "Payment Mode":
return (await MasterRespository.getPaymentMode()).data; return (await MasterRespository.getPaymentMode()).data;
case "Expense Status": case "Expense Status":
@ -363,10 +363,11 @@ const useMaster = () => {
}; };
export default useMaster; export default useMaster;
//#endregion
// ================================Mutation==================================== // ================================Mutation====================================
// Job Role----------------------------------- //#region Job Role
export const useUpdateJobRole = (onSuccessCallback, onErrorCallback) => { export const useUpdateJobRole = (onSuccessCallback, onErrorCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -411,9 +412,10 @@ export const useCreateJobRole = (onSuccessCallback) => {
}, },
}); });
}; };
//#endregion Job Role
// Application Role-------------------------------------------
//#region Application Role
export const useCreateApplicationRole = (onSuccessCallback) => { export const useCreateApplicationRole = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -456,8 +458,9 @@ export const useUpdateApplicationRole = (onSuccessCallback) => {
}, },
}); });
}; };
//#endregion
//-----Create work Category------------------------------- //#region Create work Category
export const useCreateWorkCategory = (onSuccessCallback) => { export const useCreateWorkCategory = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -500,8 +503,9 @@ export const useUpdateWorkCategory = (onSuccessCallback) => {
}, },
}); });
}; };
//#endregion
//-- Contact Category--------------------------- //#region Contact Category
export const useCreateContactCategory = (onSuccessCallback) => { export const useCreateContactCategory = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -549,7 +553,9 @@ export const useUpdateContactCategory = (onSuccessCallback) => {
}); });
}; };
// ---------Contact Tag------------------- //#endregion
//#region Contact Tag
export const useCreateContactTag = (onSuccessCallback) => { export const useCreateContactTag = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -593,14 +599,15 @@ export const useUpdateContactTag = (onSuccessCallback) => {
}, },
}); });
}; };
//#endregion
// ----------------------Expense Type------------------ //#region Expense Category
export const useCreateExpenseType = (onSuccessCallback) => { export const useCreateExpenseCategory = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async (payload) => { mutationFn: async (payload) => {
const resp = await MasterRespository.createExpenseType(payload); const resp = await MasterRespository.createExpenseCategory(payload);
return resp.data; return resp.data;
}, },
onSuccess: (data) => { onSuccess: (data) => {
@ -615,12 +622,12 @@ export const useCreateExpenseType = (onSuccessCallback) => {
}, },
}); });
}; };
export const useUpdateExpenseType = (onSuccessCallback) => { export const useUpdateExpenseCategory = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async ({ id, payload }) => { mutationFn: async ({ id, payload }) => {
const response = await MasterRespository.updateExpenseType(id, payload); const response = await MasterRespository.updateExpenseCategory(id, payload);
return response.data; return response.data;
}, },
onSuccess: (data, variables) => { onSuccess: (data, variables) => {
@ -637,7 +644,9 @@ export const useUpdateExpenseType = (onSuccessCallback) => {
}); });
}; };
// -----------------Payment Mode ------------- //#endregion
//#region Payment Mode
export const useCreatePaymentMode = (onSuccessCallback) => { export const useCreatePaymentMode = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -681,6 +690,8 @@ export const useUpdatePaymentMode = (onSuccessCallback) => {
}); });
}; };
//#endregion
// Services------------------------------- // Services-------------------------------
// export const useCreateService = (onSuccessCallback) => { // export const useCreateService = (onSuccessCallback) => {
@ -704,7 +715,7 @@ export const useUpdatePaymentMode = (onSuccessCallback) => {
// }, // },
// }); // });
// }; // };
//#region Services
export const useCreateService = (onSuccessCallback) => { export const useCreateService = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -760,6 +771,10 @@ export const useUpdateService = (onSuccessCallback) => {
}); });
}; };
//#endregion
//#region Activity Grouph
export const useCreateActivityGroup = (onSuccessCallback) => { export const useCreateActivityGroup = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -819,7 +834,10 @@ export const useUpdateActivityGroup = (onSuccessCallback) => {
}, },
}); });
}; };
// Activity------------------------------
//#endregion
//#region Activities
export const useCreateActivity = (onSuccessCallback) => { export const useCreateActivity = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -871,7 +889,9 @@ export const useUpdateActivity = (onSuccessCallback) => {
}); });
}; };
// -------------------Expense Status---------------------------------- //#endregion
//#region Expense Status
export const useCreateExpenseStatus = (onSuccessCallback) => { export const useCreateExpenseStatus = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -912,8 +932,9 @@ export const useUpdateExpenseStatus = (onSuccessCallback) => {
}, },
}); });
}; };
//#endregion
// --------------------Document-Category-------------------------------- //#region Document-Category
export const useCreateDocumentCatgory = (onSuccessCallback) => { export const useCreateDocumentCatgory = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -957,8 +978,9 @@ export const useUpdateDocumentCategory = (onSuccessCallback) => {
}, },
}); });
}; };
//#endregion
// ------------------------------Document-Type----------------------------------- //#region Document-Type
export const useCreateDocumentType = (onSuccessCallback) => { export const useCreateDocumentType = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -1000,9 +1022,9 @@ export const useUpdateDocumentType = (onSuccessCallback) => {
}, },
}); });
}; };
// ------------------------------x-x--------x-x------------------------------------ //#endregion
// ==============================Payment Adjustment Head ============================= //#region Payment Adjustment Head
export const useCreatePaymentAjustmentHead = (onSuccessCallback) => { export const useCreatePaymentAjustmentHead = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -1043,9 +1065,9 @@ export const useUpdatePaymentAjustmentHead = (onSuccessCallback) => {
}, },
}); });
}; };
// ====================x=x====================x=x================================== //#endregion
// --------Delete Master -------- //#region ==Delete Master==
export const useDeleteMasterItem = () => { export const useDeleteMasterItem = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -1078,6 +1100,8 @@ export const useDeleteMasterItem = () => {
}); });
}; };
//#endregion
export const useDeleteServiceGroup = () => { export const useDeleteServiceGroup = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();

View File

@ -50,7 +50,7 @@ export const MasterRespository = {
"Contact Category": (id) => api.delete(`/api/master/contact-category/${id}`), "Contact Category": (id) => api.delete(`/api/master/contact-category/${id}`),
"Contact Tag": (id) => api.delete(`/api/master/contact-tag/${id}`), "Contact Tag": (id) => api.delete(`/api/master/contact-tag/${id}`),
"Expense Type": (id, isActive) => "Expense Type": (id, isActive) =>
api.delete(`/api/Master/expenses-type/delete/${id}`, (isActive = false)), api.delete(`/api/Master/expenses-category/delete/${id}`, (isActive = false)),
"Payment Mode": (id, isActive) => "Payment Mode": (id, isActive) =>
api.delete(`/api/Master/payment-mode/delete/${id}`, (isActive = false)), api.delete(`/api/Master/payment-mode/delete/${id}`, (isActive = false)),
"Expense Status": (id, isActive) => "Expense Status": (id, isActive) =>
@ -78,10 +78,10 @@ export const MasterRespository = {
getAuditStatus: () => api.get("/api/Master/work-status"), getAuditStatus: () => api.get("/api/Master/work-status"),
getExpenseType: () => api.get("/api/Master/expenses-types"), getExpenseCategories: () => api.get("/api/Master/expenses-categories"),
createExpenseType: (data) => api.post("/api/Master/expenses-type", data), createExpenseCategory: (data) => api.post("/api/Master/expenses-category", data),
updateExpenseType: (id, data) => updateExpenseCategory: (id, data) =>
api.put(`/api/Master/expenses-type/edit/${id}`, data), api.put(`/api/Master/expenses-category/edit/${id}`, data),
getPaymentMode: () => api.get("/api/Master/payment-modes"), getPaymentMode: () => api.get("/api/Master/payment-modes"),
createPaymentMode: (data) => api.post(`/api/Master/payment-mode`, data), createPaymentMode: (data) => api.post(`/api/Master/payment-mode`, data),

View File

@ -1,12 +1,13 @@
export const BASE_URL = process.env.VITE_BASE_URL;
// export const BASE_URL = "https://api.marcoaiot.com";
export const THRESH_HOLD = 48; // hours export const THRESH_HOLD = 48; // hours
export const DURATION_TIME = 10; // minutes export const DURATION_TIME = 10; // minutes
export const ITEMS_PER_PAGE = 20; export const ITEMS_PER_PAGE = 20;
export const OTP_EXPIRY_SECONDS = 300; // OTP time export const OTP_EXPIRY_SECONDS = 300; // OTP time
export const BASE_URL = process.env.VITE_BASE_URL;
// export const BASE_URL = "https://api.marcoaiot.com";
export const MANAGE_MASTER = "588a8824-f924-4955-82d8-fc51956cf323"; export const MANAGE_MASTER = "588a8824-f924-4955-82d8-fc51956cf323";
export const VIEW_MASTER = "5ffbafe0-7ab0-48b1-bb50-c1bf76b65f9d"; export const VIEW_MASTER = "5ffbafe0-7ab0-48b1-bb50-c1bf76b65f9d";
@ -49,7 +50,7 @@ export const DIRECTORY_ADMIN = "4286a13b-bb40-4879-8c6d-18e9e393beda";
export const DIRECTORY_MANAGER = "62668630-13ce-4f52-a0f0-db38af2230c5"; export const DIRECTORY_MANAGER = "62668630-13ce-4f52-a0f0-db38af2230c5";
export const DIRECTORY_USER = "0f919170-92d4-4337-abd3-49b66fc871bb"; export const DIRECTORY_USER = "0f919170-92d4-4337-abd3-49b66fc871bb";
// ========================Finance=========================================================
// -----------------------Expense---------------------------------------- // -----------------------Expense----------------------------------------
export const VIEW_SELF_EXPENSE = "385be49f-8fde-440e-bdbc-3dffeb8dd116"; export const VIEW_SELF_EXPENSE = "385be49f-8fde-440e-bdbc-3dffeb8dd116";
@ -63,7 +64,16 @@ export const APPROVE_EXPENSE = "eaafdd76-8aac-45f9-a530-315589c6deca";
export const PROCESS_EXPENSE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11"; export const PROCESS_EXPENSE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11";
export const EXPENSE_MANAGE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11"; export const EXPENSE_MANAGE = "bdee29a2-b73b-402d-8dd1-c4b1f81ccbc3";
export const EXPENSE_REJECTEDBY = [
"965eda62-7907-4963-b4a1-657fb0b2724b",
"d1ee5eec-24b6-4364-8673-a8f859c60729",
];
export const EXPENSE_DRAFT = "297e0d8f-f668-41b5-bfea-e03b354251c8";
// --------------------------------Collection---------------------------- // --------------------------------Collection----------------------------
@ -73,15 +83,6 @@ export const CREATE_COLLECTION = "b93141fd-dbd3-4051-8f57-bf25d18e3555";
export const EDIT_COLLECTION = "455187b4-fef1-41f9-b3d0-025d0b6302c3"; export const EDIT_COLLECTION = "455187b4-fef1-41f9-b3d0-025d0b6302c3";
export const ADDPAYMENT_COLLECTION = "061d9ccd-85b4-4cb0-be06-2f9f32cebb72"; export const ADDPAYMENT_COLLECTION = "061d9ccd-85b4-4cb0-be06-2f9f32cebb72";
// ==========================================================================================
export const EXPENSE_REJECTEDBY = [
"d1ee5eec-24b6-4364-8673-a8f859c60729",
"965eda62-7907-4963-b4a1-657fb0b2724b",
];
export const EXPENSE_DRAFT = "297e0d8f-f668-41b5-bfea-e03b354251c8";
// ----------------------------Tenant------------------------- // ----------------------------Tenant-------------------------
export const SUPPER_TENANT = "d032cb1a-3f30-462c-bef0-7ace73a71c0b"; export const SUPPER_TENANT = "d032cb1a-3f30-462c-bef0-7ace73a71c0b";
export const MANAGE_TENANTS = "00e20637-ce8d-4417-bec4-9b31b5e65092"; export const MANAGE_TENANTS = "00e20637-ce8d-4417-bec4-9b31b5e65092";
@ -99,7 +100,8 @@ export const VERIFY_DOCUMENT = "13a1f30f-38d1-41bf-8e7a-b75189aab8e0";
// 1 - Expense Manage // 1 - Expense Manage
export const EXPENSE_MANAGEMENT = "a4e25142-449b-4334-a6e5-22f70e4732d7"; export const EXPENSE_MANAGEMENT = "a4e25142-449b-4334-a6e5-22f70e4732d7";
export const INR_CURRENCY_CODE = "78e96e4a-7ce0-4164-ae3a-c833ad45ec2c";
export const EXPENSE_PROCESSED = "61578360-3a49-4c34-8604-7b35a3787b95";
export const TENANT_STATUS = [ export const TENANT_STATUS = [
{ id: "62b05792-5115-4f99-8ff5-e8374859b191", name: "Active" }, { id: "62b05792-5115-4f99-8ff5-e8374859b191", name: "Active" },
{ id: "c0b5def8-087e-4235-b3a4-8e2f0ed91b94", name: "In Active" }, { id: "c0b5def8-087e-4235-b3a4-8e2f0ed91b94", name: "In Active" },
@ -155,12 +157,53 @@ export const PROJECT_STATUS = [
}, },
]; ];
export const DEFAULT_CURRENCY = "78e96e4a-7ce0-4164-ae3a-c833ad45ec2c";
export const EXPENSE_STATUS = { export const EXPENSE_STATUS = {
daft:"297e0d8f-f668-41b5-bfea-e03b354251c8", daft: "297e0d8f-f668-41b5-bfea-e03b354251c8",
review_pending:"6537018f-f4e9-4cb3-a210-6c3b2da999d7", review_pending: "6537018f-f4e9-4cb3-a210-6c3b2da999d7",
payment_pending:"f18c5cfd-7815-4341-8da2-2c2d65778e27", payment_pending: "f18c5cfd-7815-4341-8da2-2c2d65778e27",
approve_pending:"4068007f-c92f-4f37-a907-bc15fe57d4d8", approve_pending: "4068007f-c92f-4f37-a907-bc15fe57d4d8",
process_pending:"61578360-3a49-4c34-8604-7b35a3787b95"
} }
export const UUID_REGEX =
/^\/employee\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
export const ALLOW_PROJECTSTATUS_ID = [
"603e994b-a27f-4e5d-a251-f3d69b0498ba",
"cdad86aa-8a56-4ff4-b633-9c629057dfef",
"b74da4c2-d07e-46f2-9919-e75e49b12731",
];
export const DEFAULT_EMPTY_STATUS_ID = "00000000-0000-0000-0000-000000000000"; export const DEFAULT_EMPTY_STATUS_ID = "00000000-0000-0000-0000-000000000000";
export const FREQUENCY_FOR_RECURRING = {
0: "Monthly",
1: "Quarterly",
2: "Half-Yearly",
3: "Yearly",
4: "Daily",
5: "Weekly"
};
export const PAYEE_RECURRING_EXPENSE = [
{
id: "da462422-13b2-45cc-a175-910a225f6fc8",
label: "Active",
},
{
id: "306856fb-5655-42eb-bf8b-808bb5e84725",
label: "Completed",
},
{
id: "3ec864d2-8bf5-42fb-ba70-5090301dd816",
label: "De-Activited",
},
{
id: "8bfc9346-e092-4a80-acbf-515ae1ef6868",
label: "Paused",
},
];