added initially expense
This commit is contained in:
parent
dcbb4a3997
commit
6a97dcf5f6
@ -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" }}
|
||||
>
|
||||
<option value="">All Types</option>
|
||||
{ExpenseTypes.map((type) => (
|
||||
{expenseCategories?.map((type) => (
|
||||
<option key={type.id} value={type.id}>
|
||||
{type.name}
|
||||
</option>
|
||||
|
||||
50
src/components/Expenses/ActiveFilters.jsx
Normal file
50
src/components/Expenses/ActiveFilters.jsx
Normal 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;
|
||||
@ -40,44 +40,47 @@ const ExpenseFilterChips = ({ filters, filterData, removeFilterChip }) => {
|
||||
if (!filterChips.length) return null;
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<div className="d-flex flex-wrap align-items-start gap-1 text-start">
|
||||
{filterChips.map((chip) => (
|
||||
<div
|
||||
key={chip.key}
|
||||
className="d-flex align-items-center flex-wrap px-2 py-1 "
|
||||
style={{ fontSize: "0.9rem", maxWidth: "100%" }}
|
||||
>
|
||||
{/* Chip Label */}
|
||||
<span className="fw-semibold me-2">{chip.label}:</span>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<div className="d-flex flex-wrap align-items-start gap-1 text-start">
|
||||
{filterChips.map((chip) => (
|
||||
<div
|
||||
key={chip.key}
|
||||
className="d-flex align-items-center flex-wrap px-2 py-1 "
|
||||
style={{ fontSize: "0.9rem", maxWidth: "100%" }}
|
||||
>
|
||||
{/* Chip Label */}
|
||||
<span className="fw-semibold me-2">{chip.label}:</span>
|
||||
|
||||
{/* Chip Items */}
|
||||
<div className="d-flex flex-wrap align-items-center gap-1">
|
||||
{chip.items.map((item) => (
|
||||
<span
|
||||
key={item.id}
|
||||
className="d-flex align-items-center bg-light rounded px-2 py-1 text-xs"
|
||||
>
|
||||
<span>{item.name}</span>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close btn-close-white btn-sm ms-2"
|
||||
style={{
|
||||
filter: "invert(1) grayscale(1)",
|
||||
opacity: 0.7,
|
||||
fontSize: "0.6rem",
|
||||
}}
|
||||
onClick={() => removeFilterChip(chip.key, item.id)}
|
||||
/>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{/* Chip Items */}
|
||||
<div className="d-flex flex-wrap align-items-center gap-1">
|
||||
{chip.items.map((item) => (
|
||||
<span
|
||||
key={item.id}
|
||||
className="d-flex align-items-center bg-light rounded px-2 py-1 text-xs"
|
||||
>
|
||||
<span>{item.name}</span>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close btn-close-white btn-sm ms-2"
|
||||
style={{
|
||||
filter: "invert(1) grayscale(1)",
|
||||
opacity: 0.7,
|
||||
fontSize: "0.6rem",
|
||||
}}
|
||||
onClick={() => removeFilterChip(chip.key, item.id)}
|
||||
/>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
@ -148,7 +148,6 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata }
|
||||
selectedProjectId,
|
||||
]);
|
||||
|
||||
|
||||
if (isLoading || isFetching) return <ExpenseFilterSkeleton />;
|
||||
if (isError && isFetched)
|
||||
return <div>Something went wrong Here- {error.message} </div>;
|
||||
@ -181,7 +180,6 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata }
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<label className="fw-semibold">Choose Date Range:</label>
|
||||
<DateRangePicker1
|
||||
placeholder="DD-MM-YYYY To DD-MM-YYYY"
|
||||
startField="startDate"
|
||||
@ -189,6 +187,7 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata }
|
||||
resetSignal={resetKey}
|
||||
defaultRange={false}
|
||||
maxDate={new Date()}
|
||||
className="w-100"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -215,9 +214,9 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata }
|
||||
valueKey="id"
|
||||
/>
|
||||
<SelectMultiple
|
||||
name="ExpenseTypeIds"
|
||||
name="ExpenseCategoryIds"
|
||||
label="Category :"
|
||||
options={data.expensesType}
|
||||
options={data.expenseCategory}
|
||||
labelKey={(item) => item.name}
|
||||
valueKey="id"
|
||||
/>
|
||||
|
||||
@ -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) => (
|
||||
<div className="d-flex align-items-center cursor-pointer"
|
||||
onClick={() => navigate(`/employee/${e.createdBy?.id}`)}>
|
||||
<div
|
||||
className="d-flex align-items-center cursor-pointer"
|
||||
onClick={() => navigate(`/employee/${e.createdBy?.id}`)}
|
||||
>
|
||||
<Avatar
|
||||
size="xs"
|
||||
classAvatar="m-0"
|
||||
@ -151,8 +160,9 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
||||
lastName={e.createdBy?.lastName}
|
||||
/>
|
||||
<span className="text-truncate">
|
||||
{`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? ""
|
||||
}`.trim() || "N/A"}
|
||||
{`${e.createdBy?.firstName ?? ""} ${
|
||||
e.createdBy?.lastName ?? ""
|
||||
}`.trim() || "N/A"}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
@ -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) => (
|
||||
<span
|
||||
className={`badge bg-label-${getColorNameFromHex(e?.status?.color) || "secondary"
|
||||
}`}
|
||||
className={`badge bg-label-${
|
||||
getColorNameFromHex(e?.status?.color) || "secondary"
|
||||
}`}
|
||||
>
|
||||
{e.status?.name || "Unknown"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
if (isInitialLoading && !data) return <ExpenseTableSkeleton />;
|
||||
const headers = [
|
||||
"Expense Category",
|
||||
"Payment Mode",
|
||||
"Submitted By",
|
||||
"Submitted",
|
||||
"Amount",
|
||||
"Status",
|
||||
"Action",
|
||||
];
|
||||
if (isInitialLoading && !data)
|
||||
return <ExpenseTableSkeleton headers={headers} />;
|
||||
if (isError) return <div>{error?.message}</div>;
|
||||
|
||||
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) && (
|
||||
<th
|
||||
key={col.key}
|
||||
className={`sorting d-table-cell`}
|
||||
className={`sorting d-table-cell `}
|
||||
aria-sort="descending"
|
||||
>
|
||||
<div className={`${col.align}`}>{col.label}</div>
|
||||
<div className={`${col.align} `}>{col.label}</div>
|
||||
</th>
|
||||
)
|
||||
)}
|
||||
@ -263,12 +292,12 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
||||
<React.Fragment key={key}>
|
||||
<tr className="tr-group text-dark">
|
||||
<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} :{" "}
|
||||
</small>{" "}
|
||||
<small className="fs-6 ms-3">
|
||||
<small className="fs-6 ms-3">
|
||||
{IsGroupedByDate
|
||||
? formatUTCToLocalTime(key)
|
||||
: key}
|
||||
@ -283,16 +312,37 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
||||
(col.isAlwaysVisible || groupBy !== col.key) && (
|
||||
<td
|
||||
key={col.key}
|
||||
className={`d-table-cell ${col.align ?? ""}`}
|
||||
className={`d-table-cell ml-2 ${
|
||||
col.align ?? ""
|
||||
} `}
|
||||
>
|
||||
{col.customRender
|
||||
? col.customRender(expense)
|
||||
: col.getValue(expense)}
|
||||
<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(expense)
|
||||
: col.getValue(expense)}
|
||||
</div>
|
||||
</td>
|
||||
)
|
||||
)}
|
||||
<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
|
||||
className="bx bx-show text-primary cursor-pointer"
|
||||
onClick={() =>
|
||||
@ -374,15 +424,15 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
{data?.data?.length > 0 && (
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={data.totalPages}
|
||||
onPageChange={paginate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{data?.data?.length > 0 && (
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={data.totalPages}
|
||||
onPageChange={paginate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
<div className="row g-2">
|
||||
{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 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>
|
||||
)}
|
||||
</>
|
||||
<div className="page-min-h overflow-auto py-1">
|
||||
<Timeline items={timelineData} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default ExpenseStatusLogs;
|
||||
|
||||
95
src/components/Expenses/Filelist.jsx
Normal file
95
src/components/Expenses/Filelist.jsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@ -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 }) => {
|
||||
</div>
|
||||
|
||||
<div className="col-md-6">
|
||||
<Label htmlFor="expensesTypeId" className="form-label" required>
|
||||
<Label htmlFor="expenseCategoryId" className="form-label" required>
|
||||
Expense Type
|
||||
</Label>
|
||||
<select
|
||||
className="form-select form-select-sm"
|
||||
id="expensesTypeId"
|
||||
{...register("expensesTypeId")}
|
||||
id="expenseCategoryId"
|
||||
{...register("expenseCategoryId")}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Select Type
|
||||
@ -251,16 +251,16 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
||||
{ExpenseLoading ? (
|
||||
<option disabled>Loading...</option>
|
||||
) : (
|
||||
ExpenseTypes?.map((expense) => (
|
||||
expenseCategories?.map((expense) => (
|
||||
<option key={expense.id} value={expense.id}>
|
||||
{expense.name}
|
||||
</option>
|
||||
))
|
||||
)}
|
||||
</select>
|
||||
{errors.expensesTypeId && (
|
||||
{errors.expenseCategoryId && (
|
||||
<small className="danger-text">
|
||||
{errors.expensesTypeId.message}
|
||||
{errors.expenseCategoryId.message}
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
<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
|
||||
className="bx bx-rotate-right fs-4 cursor-pointer"
|
||||
title="Rotate Right"
|
||||
className="bx bx-rotate-right cursor-pointer"
|
||||
onClick={() => setRotation((prev) => prev + 90)}
|
||||
></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
|
||||
className="position-relative d-flex flex-column justify-content-center align-items-center"
|
||||
style={{ minHeight: "80vh" }}
|
||||
>
|
||||
|
||||
<div
|
||||
ref={containerRef}
|
||||
onMouseDown={handleMouseDown}
|
||||
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 && (
|
||||
<div className="text-secondary text-center position-absolute">
|
||||
Loading...
|
||||
</div>
|
||||
)}
|
||||
{loading && (
|
||||
<div className="text-secondary text-center mb-2">Loading...</div>
|
||||
)}
|
||||
|
||||
<div className="mb-3 d-flex justify-content-center align-items-center">
|
||||
<img
|
||||
src={imageUrl}
|
||||
alt="Preview"
|
||||
onLoad={() => 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)}
|
||||
/>
|
||||
</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
|
||||
className="btn btn-sm btn-outline-secondary"
|
||||
onClick={handleReset}
|
||||
title="Reset View"
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={() => setRotation(0)}
|
||||
title="Reset Rotation"
|
||||
>
|
||||
<i className="bx bx-reset"></i> Reset View
|
||||
<i className="bx bx-reset"></i> Reset
|
||||
</button>
|
||||
</div> */}
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PreviewDocument;
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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 (
|
||||
<form className="container px-3" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="row mb-3">
|
||||
<div className="col-12 mb-3">
|
||||
<h5 className="fw-semibold">Expense Details</h5>
|
||||
<hr />
|
||||
</div>
|
||||
<div className="text-start mb-2">
|
||||
<div className="text-muted">{data?.description}</div>
|
||||
</div>
|
||||
{/* Row 1 */}
|
||||
<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" }}
|
||||
>
|
||||
Transaction Date :
|
||||
</label>
|
||||
<div className="text-muted">
|
||||
{formatUTCToLocalTime(data?.transactionDate)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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" }}
|
||||
>
|
||||
Expense Type :
|
||||
</label>
|
||||
<div className="text-muted">{data?.expensesType?.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12 mb-1">
|
||||
<h5 className="fw-semibold m-0">Expense Details</h5>
|
||||
</div>
|
||||
|
||||
{/* Row 2 */}
|
||||
<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" }}
|
||||
>
|
||||
Supplier :
|
||||
</label>
|
||||
<div className="text-muted">{data?.supplerName}</div>
|
||||
</div>
|
||||
</div>
|
||||
<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" }}
|
||||
>
|
||||
Amount :
|
||||
</label>
|
||||
<div className="text-muted">₹ {data.amount}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Row 3 */}
|
||||
<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" }}
|
||||
>
|
||||
Payment Mode :
|
||||
</label>
|
||||
<div className="text-muted">{data?.paymentMode?.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
{data?.gstNumber && (
|
||||
<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" }}
|
||||
<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
|
||||
>
|
||||
GST Number :
|
||||
</label>
|
||||
<div className="text-muted">{data?.gstNumber}</div>
|
||||
</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="d-flex">
|
||||
<label
|
||||
className="form-label me-2 mb-0 fw-semibold text-start"
|
||||
style={{ minWidth: "130px" }}
|
||||
>
|
||||
Pre-Approved :
|
||||
</label>
|
||||
<div className="text-muted">{data.preApproved ? "Yes" : "No"}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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" }}
|
||||
>
|
||||
Project :
|
||||
</label>
|
||||
<div className="text-muted">{data?.project?.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
<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" }}
|
||||
>
|
||||
Created At :
|
||||
</label>
|
||||
<div className="text-muted">
|
||||
{formatUTCToLocalTime(data?.createdAt, true)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Row 6 */}
|
||||
{data.createdBy && (
|
||||
<div className="col-md-6 text-start">
|
||||
<div className="d-flex align-items-center">
|
||||
<label
|
||||
className="form-label me-2 mb-0 fw-semibold"
|
||||
style={{ minWidth: "130px" }}
|
||||
>
|
||||
Created By :
|
||||
</label>
|
||||
<div className="d-flex align-items-center">
|
||||
<Avatar
|
||||
size="xs"
|
||||
classAvatar="m-0"
|
||||
firstName={data.createdBy?.firstName}
|
||||
lastName={data.createdBy?.lastName}
|
||||
/>
|
||||
<span className="text-muted">
|
||||
{`${data.createdBy?.firstName ?? ""} ${
|
||||
data.createdBy?.lastName ?? ""
|
||||
}`.trim() || "N/A"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="col-md-6 text-start">
|
||||
<div className="d-flex align-items-center">
|
||||
<label
|
||||
className="form-label me-2 mb-0 fw-semibold"
|
||||
style={{ minWidth: "130px" }}
|
||||
>
|
||||
Paid By:
|
||||
</label>
|
||||
<div className="d-flex align-items-center ">
|
||||
<Avatar
|
||||
size="xs"
|
||||
classAvatar="m-0"
|
||||
firstName={data.paidBy?.firstName}
|
||||
lastName={data.paidBy?.lastName}
|
||||
/>
|
||||
<span className="text-muted">
|
||||
{`${data.paidBy?.firstName ?? ""} ${
|
||||
data.paidBy?.lastName ?? ""
|
||||
}`.trim() || "N/A"}
|
||||
{data?.status?.name}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-12 text-start">
|
||||
<label className="form-label me-2 mb-2 fw-semibold">Attachment :</label>
|
||||
|
||||
<div className="d-flex flex-wrap gap-2">
|
||||
{data?.documents?.map((doc) => {
|
||||
const isImage = doc.contentType?.startsWith("image");
|
||||
|
||||
return (
|
||||
<div
|
||||
key={doc.documentId}
|
||||
className="border rounded hover-scale p-2 d-flex flex-column align-items-center"
|
||||
style={{
|
||||
width: "80px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => {
|
||||
if (isImage) {
|
||||
setDocumentView({
|
||||
IsOpen: true,
|
||||
Image: doc.preSignedUrl,
|
||||
});
|
||||
} else {
|
||||
window.open(doc.preSignedUrl, "_blank");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className={`bx ${getIconByFileType(doc.contentType)}`}
|
||||
style={{ fontSize: "30px" }}
|
||||
></i>
|
||||
<small
|
||||
className="text-center text-tiny text-truncate w-100"
|
||||
title={doc.fileName}
|
||||
{/* Row 1 */}
|
||||
<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" }}
|
||||
>
|
||||
{doc.fileName}
|
||||
</small>
|
||||
Transaction Date :
|
||||
</label>
|
||||
<div className="text-muted">
|
||||
{formatUTCToLocalTime(data?.transactionDate)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}) ?? "No Attachment"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{data.expensesReimburse && (
|
||||
<div className="row text-start mt-2">
|
||||
<div className="col-md-6 mb-sm-0 mb-2">
|
||||
<label className="form-label me-2 mb-0 fw-semibold">
|
||||
Transaction ID :
|
||||
</label>
|
||||
{data.expensesReimburse.reimburseTransactionId || "N/A"}
|
||||
</div>
|
||||
<div className="col-md-6 ">
|
||||
<label className="form-label me-2 mb-0 fw-semibold">
|
||||
Reimburse Date :
|
||||
</label>
|
||||
{formatUTCToLocalTime(data.expensesReimburse.reimburseDate)}
|
||||
</div>
|
||||
<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" }}
|
||||
>
|
||||
Expense Category :
|
||||
</label>
|
||||
<div className="text-muted">{data?.expenseCategory?.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{data.expensesReimburse && (
|
||||
<>
|
||||
<div className="col-md-6 d-flex align-items-center">
|
||||
<label className="form-label me-2 mb-0 fw-semibold">
|
||||
Reimburse By :
|
||||
{/* Row 2 */}
|
||||
<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" }}
|
||||
>
|
||||
Supplier :
|
||||
</label>
|
||||
<div className="text-muted">{data?.supplerName}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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" }}
|
||||
>
|
||||
Amount :
|
||||
</label>
|
||||
<div className="text-muted">
|
||||
{" "}
|
||||
{formatFigure(data?.amount, {
|
||||
type: "currency",
|
||||
currency: data?.currency?.currencyCode ?? "INR",
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Row 3 */}
|
||||
<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" }}
|
||||
>
|
||||
Payment Mode :
|
||||
</label>
|
||||
<div className="text-muted">{data?.paymentMode?.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{data?.gstNumber && (
|
||||
<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" }}
|
||||
>
|
||||
GST Number :
|
||||
</label>
|
||||
<div className="text-muted">{data?.gstNumber}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<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" }}
|
||||
>
|
||||
Pre-Approved :
|
||||
</label>
|
||||
<div className="text-muted">
|
||||
{data.preApproved ? "Yes" : "No"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Row 5 */}
|
||||
<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" }}
|
||||
>
|
||||
Project :
|
||||
</label>
|
||||
<div className="text-muted">{data?.project?.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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" }}
|
||||
>
|
||||
Created At :
|
||||
</label>
|
||||
<div className="text-muted">
|
||||
{formatUTCToLocalTime(data?.createdAt, true)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Created & Paid By */}
|
||||
{data.createdBy && (
|
||||
<div className="col-md-6 text-start mb-3">
|
||||
<div className="d-flex align-items-center">
|
||||
<label
|
||||
className="form-label me-2 mb-0 fw-semibold"
|
||||
style={{ minWidth: "130px" }}
|
||||
>
|
||||
Created By :
|
||||
</label>
|
||||
<Avatar
|
||||
size="xs"
|
||||
classAvatar="m-0 me-1"
|
||||
firstName={data.createdBy?.firstName}
|
||||
lastName={data.createdBy?.lastName}
|
||||
/>
|
||||
<span className="text-muted">
|
||||
{`${data.createdBy?.firstName ?? ""} ${
|
||||
data.createdBy?.lastName ?? ""
|
||||
}`.trim() || "N/A"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="col-md-6 text-start mb-3">
|
||||
<div className="d-flex align-items-center">
|
||||
<label
|
||||
className="form-label me-2 mb-0 fw-semibold"
|
||||
style={{ minWidth: "130px" }}
|
||||
>
|
||||
Paid By :
|
||||
</label>
|
||||
<Avatar
|
||||
size="xs"
|
||||
classAvatar="m-0 me-1"
|
||||
firstName={data?.expensesReimburse?.reimburseBy?.firstName}
|
||||
lastName={data?.expensesReimburse?.reimburseBy?.lastName}
|
||||
firstName={data.paidBy?.firstName}
|
||||
lastName={data.paidBy?.lastName}
|
||||
/>
|
||||
<span className="text-muted">
|
||||
{`${data?.expensesReimburse?.reimburseBy?.firstName} ${data?.expensesReimburse?.reimburseBy?.lastName}`.trim()}
|
||||
{`${data.paidBy?.firstName ?? ""} ${
|
||||
data.paidBy?.lastName ?? ""
|
||||
}`.trim() || "N/A"}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<hr className="divider my-1 border-2 divider-primary my-2" />
|
||||
</div>
|
||||
|
||||
{Array.isArray(data?.nextStatus) && data.nextStatus.length > 0 && (
|
||||
<>
|
||||
{IsPaymentProcess && nextStatusWithPermission?.length > 0 && (
|
||||
<div className="row">
|
||||
<div className="col-12 col-md-6 text-start">
|
||||
<label className="form-label">Transaction Id </label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
{...register("reimburseTransactionId")}
|
||||
/>
|
||||
{errors.reimburseTransactionId && (
|
||||
<small className="danger-text">
|
||||
{errors.reimburseTransactionId.message}
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-12 col-md-6 text-start">
|
||||
<label className="form-label">Transaction Date </label>
|
||||
<DatePicker
|
||||
name="reimburseDate"
|
||||
control={control}
|
||||
minDate={data?.transactionDate}
|
||||
maxDate={new Date()}
|
||||
/>
|
||||
{errors.reimburseDate && (
|
||||
<small className="danger-text">
|
||||
{errors.reimburseDate.message}
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-12 col-md-6 text-start">
|
||||
<label className="form-label">Reimburse By </label>
|
||||
<EmployeeSearchInput
|
||||
control={control}
|
||||
name="reimburseById"
|
||||
projectId={null}
|
||||
/>
|
||||
{/* 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>
|
||||
|
||||
{/* Attachments */}
|
||||
<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">
|
||||
{data?.documents?.map((doc) => {
|
||||
const isImage = doc.contentType?.includes("image");
|
||||
return (
|
||||
<div
|
||||
key={doc.documentId}
|
||||
className="d-flex align-items-center cusor-pointer"
|
||||
onClick={() => {
|
||||
if (isImage) {
|
||||
setDocumentView({
|
||||
IsOpen: true,
|
||||
Image: doc.preSignedUrl,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className={`bx ${getIconByFileType(doc.contentType)}`}
|
||||
style={{ fontSize: "30px" }}
|
||||
></i>
|
||||
<small
|
||||
className="text-center text-tiny text-truncate w-100"
|
||||
title={doc.fileName}
|
||||
>
|
||||
{doc.fileName}
|
||||
</small>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="col-12 mb-3 text-start">
|
||||
{((nextStatusWithPermission.length > 0 && !IsRejectedExpense) ||
|
||||
(IsRejectedExpense && isCreatedBy)) && (
|
||||
<>
|
||||
<Label className="form-label me-2 mb-0" required>
|
||||
Comment
|
||||
</Label>
|
||||
<textarea
|
||||
className="form-control form-control-sm"
|
||||
{...register("comment")}
|
||||
rows="2"
|
||||
/>
|
||||
{errors.comment && (
|
||||
<small className="danger-text">
|
||||
{errors.comment.message}
|
||||
</small>
|
||||
|
||||
{data.expensesReimburse && (
|
||||
<div className="row text-start mt-2">
|
||||
<div className="col-md-6 mb-sm-0 mb-2">
|
||||
<label className="form-label me-2 mb-0 fw-semibold">
|
||||
Transaction ID :
|
||||
</label>
|
||||
{data.expensesReimburse.reimburseTransactionId || "N/A"}
|
||||
</div>
|
||||
<div className="col-md-6 ">
|
||||
<label className="form-label me-2 mb-0 fw-semibold">
|
||||
Reimburse Date :
|
||||
</label>
|
||||
{formatUTCToLocalTime(data.expensesReimburse.reimburseDate)}
|
||||
</div>
|
||||
|
||||
{data.expensesReimburse && (
|
||||
<>
|
||||
<div className="col-md-6 d-flex align-items-center">
|
||||
<label className="form-label me-2 mb-0 fw-semibold">
|
||||
Reimburse By :
|
||||
</label>
|
||||
<Avatar
|
||||
size="xs"
|
||||
classAvatar="m-0 me-1"
|
||||
firstName={
|
||||
data?.expensesReimburse?.reimburseBy?.firstName
|
||||
}
|
||||
lastName={
|
||||
data?.expensesReimburse?.reimburseBy?.lastName
|
||||
}
|
||||
/>
|
||||
<span className="text-muted">
|
||||
{`${data?.expensesReimburse?.reimburseBy?.firstName} ${data?.expensesReimburse?.reimburseBy?.lastName}`.trim()}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* <hr className="divider my-1 border-2 divider-primary my-2" /> */}
|
||||
|
||||
{Array.isArray(data?.nextStatus) && data.nextStatus.length > 0 && (
|
||||
<>
|
||||
{IsPaymentProcess && nextStatusWithPermission?.length > 0 && (
|
||||
<div className="row ">
|
||||
<div className="col-12 col-md-6 text-start">
|
||||
<Label className="form-label" required>Transaction Id </Label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
{...register("reimburseTransactionId")}
|
||||
/>
|
||||
{errors.reimburseTransactionId && (
|
||||
<small className="danger-text">
|
||||
{errors.reimburseTransactionId.message}
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-12 col-md-6 text-start mb-1">
|
||||
<Label className="form-label" required>Transaction Date </Label>
|
||||
<DatePicker className="w-100"
|
||||
name="reimburseDate"
|
||||
control={control}
|
||||
minDate={data?.transactionDate}
|
||||
maxDate={new Date()}
|
||||
/>
|
||||
{errors.reimburseDate && (
|
||||
<small className="danger-text">
|
||||
{errors.reimburseDate.message}
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-12 col-md-6 text-start mb-1">
|
||||
<Label className="form-label" required>
|
||||
Reimburse By{" "}
|
||||
</Label>
|
||||
<EmployeeSearchInput
|
||||
control={control}
|
||||
name="reimburseById"
|
||||
projectId={null}
|
||||
/>
|
||||
</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 className="col-12 mb-3 text-start mt-1">
|
||||
{((nextStatusWithPermission.length > 0 &&
|
||||
!IsRejectedExpense) ||
|
||||
(IsRejectedExpense && isCreatedBy)) && (
|
||||
<>
|
||||
<Label className="form-label me-2 mb-0" required>
|
||||
Comment
|
||||
</Label>
|
||||
<textarea
|
||||
className="form-control form-control-sm"
|
||||
{...register("comment")}
|
||||
rows="2"
|
||||
/>
|
||||
{errors.comment && (
|
||||
<small className="danger-text">
|
||||
{errors.comment.message}
|
||||
</small>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{nextStatusWithPermission?.length > 0 &&
|
||||
(!IsRejectedExpense || isCreatedBy) && (
|
||||
<div className="text-end flex-wrap gap-2 my-2 mt-3">
|
||||
{nextStatusWithPermission.map((status, index) => (
|
||||
<button
|
||||
key={status.id || index}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setClickedStatusId(status.id);
|
||||
setValue("statusId", status.id);
|
||||
handleSubmit(onSubmit)();
|
||||
}}
|
||||
disabled={isPending || isFetching}
|
||||
className="btn btn-primary btn-sm cursor-pointer mx-2 border-0"
|
||||
>
|
||||
{isPending && clickedStatusId === status.id
|
||||
? "Please Wait..."
|
||||
: status.displayName || status.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{nextStatusWithPermission?.length > 0 &&
|
||||
(!IsRejectedExpense || isCreatedBy) && (
|
||||
<div className="text-end flex-wrap gap-2 my-2 mt-3">
|
||||
{nextStatusWithPermission.map((status, index) => (
|
||||
<button
|
||||
key={status.id || index}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setClickedStatusId(status.id);
|
||||
setValue("statusId", status.id);
|
||||
handleSubmit(onSubmit)();
|
||||
}}
|
||||
disabled={isPending || isFetching}
|
||||
className="btn btn-primary btn-sm cursor-pointer mx-2 border-0"
|
||||
>
|
||||
{isPending && clickedStatusId === status.id
|
||||
? "Please Wait..."
|
||||
: status.displayName || status.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ExpenseStatusLogs data={data} />
|
||||
<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} />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
104
src/components/common/TimeLine.jsx
Normal file
104
src/components/common/TimeLine.jsx
Normal 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;
|
||||
24
src/components/common/Tooltip.jsx
Normal file
24
src/components/common/Tooltip.jsx
Normal 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;
|
||||
@ -2,10 +2,7 @@ import React, { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import {
|
||||
useCreateExpenseType,
|
||||
useUpdateExpenseType,
|
||||
} from "../../hooks/masterHook/useMaster";
|
||||
import { useCreateExpenseCategory, useUpdateExpenseCategory } from "../../hooks/masterHook/useMaster";
|
||||
import Label from "../common/Label";
|
||||
|
||||
const ExpnseSchema = z.object({
|
||||
@ -14,7 +11,7 @@ const ExpnseSchema = z.object({
|
||||
description: z.string().min(1, { message: "Description is required" }),
|
||||
});
|
||||
|
||||
const ManageExpenseType = ({ data = null, onClose }) => {
|
||||
const ManageExpenseCategory = ({ data = null, onClose }) => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
@ -24,21 +21,21 @@ const ManageExpenseType = ({ data = null, onClose }) => {
|
||||
resolver: zodResolver(ExpnseSchema),
|
||||
defaultValues: { name: "", noOfPersonsRequired: false, description: "" },
|
||||
});
|
||||
const { mutate: UpdateExpenseType, isPending:isPendingUpdate } = useUpdateExpenseType(
|
||||
const { mutate: UpdateExpenseCategory, isPending:isPendingUpdate } = useUpdateExpenseCategory(
|
||||
() => onClose?.()
|
||||
);
|
||||
const { mutate: CreateExpenseType, isPending } = useCreateExpenseType(() =>
|
||||
const { mutate: CreateExpenseCategory, isPending } = useCreateExpenseCategory(() =>
|
||||
onClose?.()
|
||||
);
|
||||
|
||||
const onSubmit = (payload) => {
|
||||
if (data) {
|
||||
UpdateExpenseType({
|
||||
UpdateExpenseCategory({
|
||||
id: data.id,
|
||||
payload: { ...payload, id: data.id },
|
||||
});
|
||||
} else {
|
||||
CreateExpenseType(payload);
|
||||
CreateExpenseCategory(payload);
|
||||
}
|
||||
};
|
||||
|
||||
@ -112,4 +109,4 @@ const ManageExpenseType = ({ data = null, onClose }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ManageExpenseType;
|
||||
export default ManageExpenseCategory;
|
||||
@ -9,7 +9,6 @@ import CreateCategory from "./CreateContactCategory";
|
||||
import CreateContactTag from "./CreateContactTag";
|
||||
import EditContactCategory from "./EditContactCategory";
|
||||
import EditContactTag from "./EditContactTag";
|
||||
import ManageExpenseType from "./ManageExpenseType";
|
||||
import ManagePaymentMode from "./ManagePaymentMode";
|
||||
import ManageExpenseStatus from "./ManageExpenseStatus";
|
||||
import ManageDocumentCategory from "./ManageDocumentCategory";
|
||||
@ -17,6 +16,7 @@ import ManageDocumentType from "./ManageDocumentType";
|
||||
import ManageServices from "./Services/ManageServices";
|
||||
import ServiceGroups from "./Services/ServicesGroups";
|
||||
import ManagePaymentHead from "./paymentAdjustmentHead/ManagePaymentHead";
|
||||
import ManageExpenseCategory from "./ManageExpenseCategory";
|
||||
|
||||
const MasterModal = ({ modaldata, closeModal }) => {
|
||||
if (!modaldata?.modalType || modaldata.modalType === "delete") {
|
||||
@ -42,8 +42,8 @@ const MasterModal = ({ modaldata, closeModal }) => {
|
||||
),
|
||||
"Contact Tag": <CreateContactTag data={item} onClose={closeModal} />,
|
||||
"Edit-Contact Tag": <EditContactTag data={item} onClose={closeModal} />,
|
||||
"Expense Type": <ManageExpenseType onClose={closeModal} />,
|
||||
"Edit-Expense Type": <ManageExpenseType data={item} onClose={closeModal} />,
|
||||
"Expense Category": <ManageExpenseCategory onClose={closeModal} />,
|
||||
"Edit-Expense Category": <ManageExpenseCategory data={item} onClose={closeModal} />,
|
||||
"Payment Mode": <ManagePaymentMode onClose={closeModal} />,
|
||||
"Edit-Payment Mode": <ManagePaymentMode data={item} onClose={closeModal} />,
|
||||
"Expense Status": <ManageExpenseStatus onClose={closeModal} />,
|
||||
|
||||
@ -150,15 +150,15 @@ export const useContactTags = () => {
|
||||
return { contactTags, loading, error };
|
||||
};
|
||||
|
||||
export const useExpenseType = () => {
|
||||
export const useExpenseCategory = () => {
|
||||
const {
|
||||
data: ExpenseTypes = [],
|
||||
data: expenseCategories = [],
|
||||
isLoading: loading,
|
||||
error,
|
||||
} = useQuery({
|
||||
queryKey: ["Expense Type"],
|
||||
queryKey: ["Expense Category"],
|
||||
queryFn: async () => {
|
||||
const res = await MasterRespository.getExpenseType();
|
||||
const res = await MasterRespository.getExpenseCategories();
|
||||
return res.data;
|
||||
},
|
||||
onError: (error) => {
|
||||
@ -275,8 +275,8 @@ export const useOrganizationType = () => {
|
||||
queryFn: async () => await MasterRespository.getOrganizationType(),
|
||||
});
|
||||
};
|
||||
// ===Application Masters Query=================================================
|
||||
|
||||
//#region ==Get Masters==
|
||||
const fetchMasterData = async (masterType) => {
|
||||
switch (masterType) {
|
||||
case "Application Role":
|
||||
@ -293,8 +293,8 @@ const fetchMasterData = async (masterType) => {
|
||||
return (await MasterRespository.getContactCategory()).data;
|
||||
case "Contact Tag":
|
||||
return (await MasterRespository.getContactTag()).data;
|
||||
case "Expense Type":
|
||||
return (await MasterRespository.getExpenseType()).data;
|
||||
case "Expense Category":
|
||||
return (await MasterRespository.getExpenseCategories()).data;
|
||||
case "Payment Mode":
|
||||
return (await MasterRespository.getPaymentMode()).data;
|
||||
case "Expense Status":
|
||||
@ -363,10 +363,11 @@ const useMaster = () => {
|
||||
};
|
||||
|
||||
export default useMaster;
|
||||
//#endregion
|
||||
|
||||
// ================================Mutation====================================
|
||||
|
||||
// Job Role-----------------------------------
|
||||
//#region Job Role
|
||||
export const useUpdateJobRole = (onSuccessCallback, onErrorCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@ -411,9 +412,10 @@ export const useCreateJobRole = (onSuccessCallback) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
//#endregion Job Role
|
||||
|
||||
// Application Role-------------------------------------------
|
||||
|
||||
//#region Application Role
|
||||
export const useCreateApplicationRole = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@ -456,8 +458,9 @@ export const useUpdateApplicationRole = (onSuccessCallback) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
//#endregion
|
||||
|
||||
//-----Create work Category-------------------------------
|
||||
//#region Create work Category
|
||||
export const useCreateWorkCategory = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@ -500,8 +503,9 @@ export const useUpdateWorkCategory = (onSuccessCallback) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
//#endregion
|
||||
|
||||
//-- Contact Category---------------------------
|
||||
//#region Contact Category
|
||||
|
||||
export const useCreateContactCategory = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
@ -549,7 +553,9 @@ export const useUpdateContactCategory = (onSuccessCallback) => {
|
||||
});
|
||||
};
|
||||
|
||||
// ---------Contact Tag-------------------
|
||||
//#endregion
|
||||
|
||||
//#region Contact Tag
|
||||
|
||||
export const useCreateContactTag = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
@ -593,14 +599,15 @@ export const useUpdateContactTag = (onSuccessCallback) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
//#endregion
|
||||
|
||||
// ----------------------Expense Type------------------
|
||||
export const useCreateExpenseType = (onSuccessCallback) => {
|
||||
//#region Expense Category
|
||||
export const useCreateExpenseCategory = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (payload) => {
|
||||
const resp = await MasterRespository.createExpenseType(payload);
|
||||
const resp = await MasterRespository.createExpenseCategory(payload);
|
||||
return resp.data;
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
@ -615,12 +622,12 @@ export const useCreateExpenseType = (onSuccessCallback) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
export const useUpdateExpenseType = (onSuccessCallback) => {
|
||||
export const useUpdateExpenseCategory = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ id, payload }) => {
|
||||
const response = await MasterRespository.updateExpenseType(id, payload);
|
||||
const response = await MasterRespository.updateExpenseCategory(id, payload);
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: (data, variables) => {
|
||||
@ -637,7 +644,9 @@ export const useUpdateExpenseType = (onSuccessCallback) => {
|
||||
});
|
||||
};
|
||||
|
||||
// -----------------Payment Mode -------------
|
||||
//#endregion
|
||||
|
||||
//#region Payment Mode
|
||||
|
||||
export const useCreatePaymentMode = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
@ -681,6 +690,8 @@ export const useUpdatePaymentMode = (onSuccessCallback) => {
|
||||
});
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
// Services-------------------------------
|
||||
|
||||
// export const useCreateService = (onSuccessCallback) => {
|
||||
@ -704,7 +715,7 @@ export const useUpdatePaymentMode = (onSuccessCallback) => {
|
||||
// },
|
||||
// });
|
||||
// };
|
||||
|
||||
//#region Services
|
||||
export const useCreateService = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@ -760,6 +771,10 @@ export const useUpdateService = (onSuccessCallback) => {
|
||||
});
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Activity Grouph
|
||||
|
||||
export const useCreateActivityGroup = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@ -819,7 +834,10 @@ export const useUpdateActivityGroup = (onSuccessCallback) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
// Activity------------------------------
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Activities
|
||||
export const useCreateActivity = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@ -871,7 +889,9 @@ export const useUpdateActivity = (onSuccessCallback) => {
|
||||
});
|
||||
};
|
||||
|
||||
// -------------------Expense Status----------------------------------
|
||||
//#endregion
|
||||
|
||||
//#region Expense Status
|
||||
export const useCreateExpenseStatus = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@ -912,8 +932,9 @@ export const useUpdateExpenseStatus = (onSuccessCallback) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
//#endregion
|
||||
|
||||
// --------------------Document-Category--------------------------------
|
||||
//#region Document-Category
|
||||
export const useCreateDocumentCatgory = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@ -957,8 +978,9 @@ export const useUpdateDocumentCategory = (onSuccessCallback) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
//#endregion
|
||||
|
||||
// ------------------------------Document-Type-----------------------------------
|
||||
//#region Document-Type
|
||||
export const useCreateDocumentType = (onSuccessCallback) => {
|
||||
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) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@ -1043,9 +1065,9 @@ export const useUpdatePaymentAjustmentHead = (onSuccessCallback) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
// ====================x=x====================x=x==================================
|
||||
//#endregion
|
||||
|
||||
// --------Delete Master --------
|
||||
//#region ==Delete Master==
|
||||
export const useDeleteMasterItem = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@ -1078,6 +1100,8 @@ export const useDeleteMasterItem = () => {
|
||||
});
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
export const useDeleteServiceGroup = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
@ -50,7 +50,7 @@ export const MasterRespository = {
|
||||
"Contact Category": (id) => api.delete(`/api/master/contact-category/${id}`),
|
||||
"Contact Tag": (id) => api.delete(`/api/master/contact-tag/${id}`),
|
||||
"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) =>
|
||||
api.delete(`/api/Master/payment-mode/delete/${id}`, (isActive = false)),
|
||||
"Expense Status": (id, isActive) =>
|
||||
@ -78,10 +78,10 @@ export const MasterRespository = {
|
||||
|
||||
getAuditStatus: () => api.get("/api/Master/work-status"),
|
||||
|
||||
getExpenseType: () => api.get("/api/Master/expenses-types"),
|
||||
createExpenseType: (data) => api.post("/api/Master/expenses-type", data),
|
||||
updateExpenseType: (id, data) =>
|
||||
api.put(`/api/Master/expenses-type/edit/${id}`, data),
|
||||
getExpenseCategories: () => api.get("/api/Master/expenses-categories"),
|
||||
createExpenseCategory: (data) => api.post("/api/Master/expenses-category", data),
|
||||
updateExpenseCategory: (id, data) =>
|
||||
api.put(`/api/Master/expenses-category/edit/${id}`, data),
|
||||
|
||||
getPaymentMode: () => api.get("/api/Master/payment-modes"),
|
||||
createPaymentMode: (data) => api.post(`/api/Master/payment-mode`, data),
|
||||
|
||||
@ -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 DURATION_TIME = 10; // minutes
|
||||
export const ITEMS_PER_PAGE = 20;
|
||||
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 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_USER = "0f919170-92d4-4337-abd3-49b66fc871bb";
|
||||
// ========================Finance=========================================================
|
||||
|
||||
// -----------------------Expense----------------------------------------
|
||||
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 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----------------------------
|
||||
|
||||
@ -73,15 +83,6 @@ export const CREATE_COLLECTION = "b93141fd-dbd3-4051-8f57-bf25d18e3555";
|
||||
export const EDIT_COLLECTION = "455187b4-fef1-41f9-b3d0-025d0b6302c3";
|
||||
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-------------------------
|
||||
export const SUPPER_TENANT = "d032cb1a-3f30-462c-bef0-7ace73a71c0b";
|
||||
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
|
||||
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 = [
|
||||
{ id: "62b05792-5115-4f99-8ff5-e8374859b191", name: "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 = {
|
||||
daft:"297e0d8f-f668-41b5-bfea-e03b354251c8",
|
||||
review_pending:"6537018f-f4e9-4cb3-a210-6c3b2da999d7",
|
||||
payment_pending:"f18c5cfd-7815-4341-8da2-2c2d65778e27",
|
||||
approve_pending:"4068007f-c92f-4f37-a907-bc15fe57d4d8",
|
||||
process_pending:"61578360-3a49-4c34-8604-7b35a3787b95"
|
||||
daft: "297e0d8f-f668-41b5-bfea-e03b354251c8",
|
||||
review_pending: "6537018f-f4e9-4cb3-a210-6c3b2da999d7",
|
||||
payment_pending: "f18c5cfd-7815-4341-8da2-2c2d65778e27",
|
||||
approve_pending: "4068007f-c92f-4f37-a907-bc15fe57d4d8",
|
||||
|
||||
}
|
||||
|
||||
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 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",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user