added initially expense
This commit is contained in:
parent
dcbb4a3997
commit
6a97dcf5f6
@ -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>
|
||||||
|
|||||||
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,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>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -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>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
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 { 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>
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
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 { 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;
|
||||||
@ -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} />,
|
||||||
|
|||||||
@ -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();
|
||||||
|
|
||||||
|
|||||||
@ -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),
|
||||||
|
|||||||
@ -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",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user