attachement display horizontically
This commit is contained in:
parent
a87d3fb20a
commit
3d7a8a1c94
@ -28,6 +28,7 @@
|
||||
<link rel="stylesheet" href="/assets/css/core-extend.css" />
|
||||
<link rel="stylesheet" href="/assets/css/default.css" />
|
||||
<link rel="stylesheet" href="/assets/css/skeleton.css" />
|
||||
<link rel="stylesheet" href="/assets/css/hover-utility.css" />
|
||||
|
||||
<link rel="stylesheet" href="/assets/vendor/libs/perfect-scrollbar/perfect-scrollbar.css" />
|
||||
|
||||
|
86
public/assets/css/hover-utility.css
Normal file
86
public/assets/css/hover-utility.css
Normal file
@ -0,0 +1,86 @@
|
||||
/* Hover background color */
|
||||
.hover-bg-light:hover {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
.hover-bg-primary:hover {
|
||||
background-color: var(--bs-primary) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.hover-bg-danger:hover {
|
||||
background-color: #dc3545 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.hover-bg-success:hover {
|
||||
background-color: var(--bg-success) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.hover-bg-warning:hover {
|
||||
background-color: #ffc107 !important;
|
||||
color: #212529 !important;
|
||||
}
|
||||
|
||||
/* Hover text color */
|
||||
.hover-text-primary:hover {
|
||||
color: #0d6efd !important;
|
||||
}
|
||||
|
||||
.hover-text-danger:hover {
|
||||
color: #dc3545 !important;
|
||||
}
|
||||
|
||||
.hover-text-success:hover {
|
||||
color: #198754 !important;
|
||||
}
|
||||
|
||||
.hover-text-muted:hover {
|
||||
color: #6c757d !important;
|
||||
}
|
||||
|
||||
/* Hover shadow */
|
||||
.hover-shadow-sm:hover {
|
||||
box-shadow: 0 .125rem .25rem rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
|
||||
.hover-shadow:hover {
|
||||
box-shadow: 0 .5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.hover-shadow-lg:hover {
|
||||
box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
}
|
||||
|
||||
/* Hover scale */
|
||||
.hover-scale:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.hover-scale-sm:hover {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.hover-scale-lg:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* Add smooth transition to hover effects */
|
||||
.hover-transition {
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
/* Hover border color */
|
||||
.hover-border-primary:hover {
|
||||
border-color: #0d6efd !important;
|
||||
}
|
||||
|
||||
.hover-border-danger:hover {
|
||||
border-color: #dc3545 !important;
|
||||
}
|
||||
|
||||
.thick-divider {
|
||||
height: 3px;
|
||||
font-size: 10px;
|
||||
}
|
@ -120,7 +120,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
||||
firstName={e.createdBy?.firstName}
|
||||
lastName={e.createdBy?.lastName}
|
||||
/>
|
||||
<span>
|
||||
<span className="text-truncate">
|
||||
{`${e.createdBy?.firstName ?? ""} ${
|
||||
e.createdBy?.lastName ?? ""
|
||||
}`.trim() || "N/A"}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React from "react";
|
||||
|
||||
|
||||
const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => (
|
||||
<div
|
||||
className={`skeleton mb-2 ${className}`}
|
||||
@ -11,13 +10,12 @@ const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => (
|
||||
></div>
|
||||
);
|
||||
|
||||
|
||||
const ExpenseSkeleton = () => {
|
||||
return (
|
||||
<div className="container p-3">
|
||||
<div className="d-flex justify-content-center">
|
||||
<SkeletonLine height={20} width="200px" />
|
||||
</div>
|
||||
<div className="d-flex justify-content-center">
|
||||
<SkeletonLine height={20} width="200px" />
|
||||
</div>
|
||||
|
||||
{[...Array(5)].map((_, idx) => (
|
||||
<div className="row my-2" key={idx}>
|
||||
@ -52,12 +50,9 @@ const ExpenseSkeleton = () => {
|
||||
|
||||
export default ExpenseSkeleton;
|
||||
|
||||
|
||||
|
||||
|
||||
export const ExpenseDetailsSkeleton = () => {
|
||||
return (
|
||||
<div className="container px-3">
|
||||
<div className="container px-3">
|
||||
<div className="row mb-3">
|
||||
<div className="d-flex justify-content-center mb-3">
|
||||
<SkeletonLine height={20} width="180px" className="mb-2" />
|
||||
@ -65,7 +60,7 @@ export const ExpenseDetailsSkeleton = () => {
|
||||
|
||||
{[...Array(3)].map((_, i) => (
|
||||
<div className="col-12 col-md-4 mb-3" key={`row-1-${i}`}>
|
||||
<SkeletonLine height={14} className="mb-1" />
|
||||
<SkeletonLine height={14} className="mb-1" />
|
||||
<SkeletonLine />
|
||||
</div>
|
||||
))}
|
||||
@ -77,30 +72,38 @@ export const ExpenseDetailsSkeleton = () => {
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="col-12 text-start">
|
||||
<SkeletonLine height={14} width="150px" className="mb-1" />
|
||||
|
||||
|
||||
<div className="col-12 my-2">
|
||||
<SkeletonLine height={14} width="100px" className="mb-2" />
|
||||
{[...Array(2)].map((_, i) => (
|
||||
<div
|
||||
className="list-group-item d-flex align-items-center mb-2"
|
||||
key={i}
|
||||
>
|
||||
<div className="d-flex flex-wrap gap-2">
|
||||
{[...Array(2)].map((_, i) => (
|
||||
<div
|
||||
className="rounded me-2"
|
||||
style={{
|
||||
height: "50px",
|
||||
width: "80px",
|
||||
backgroundColor: "#dcdcdc",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
/>
|
||||
<div className="w-100">
|
||||
<SkeletonLine height={14} width="60%" className="mb-1" />
|
||||
<SkeletonLine height={14} width="20%" />
|
||||
key={i}
|
||||
className="border rounded p-2 d-flex flex-column align-items-center"
|
||||
style={{ width: "80px" }}
|
||||
>
|
||||
{/* Icon placeholder */}
|
||||
<div
|
||||
style={{
|
||||
height: "30px",
|
||||
width: "30px",
|
||||
|
||||
borderRadius: "4px",
|
||||
marginBottom: "6px",
|
||||
}}
|
||||
className="skeleton"
|
||||
/>
|
||||
{/* Filename placeholder */}
|
||||
<div
|
||||
style={{
|
||||
height: "10px",
|
||||
width: "100%",
|
||||
}}
|
||||
className="skeleton"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="divider my-1" />
|
||||
@ -123,7 +126,12 @@ export const ExpenseDetailsSkeleton = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const SkeletonCell = ({ width = "100%", height = 20, className = "", style = {} }) => (
|
||||
const SkeletonCell = ({
|
||||
width = "100%",
|
||||
height = 20,
|
||||
className = "",
|
||||
style = {},
|
||||
}) => (
|
||||
<div
|
||||
className={`skeleton ${className}`}
|
||||
style={{
|
||||
@ -137,93 +145,108 @@ const SkeletonCell = ({ width = "100%", height = 20, className = "", style = {}
|
||||
|
||||
export const ExpenseTableSkeleton = ({ groups = 3, rowsPerGroup = 3 }) => {
|
||||
return (
|
||||
<div className="card px-2">
|
||||
<div className="card px-2">
|
||||
<table
|
||||
className="card-body table border-top dataTable no-footer dtr-column text-nowrap"
|
||||
aria-describedby="DataTables_Table_0_info"
|
||||
id="horizontal-example"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="d-none d-sm-table-cell">
|
||||
<div className="text-start ms-5">Expense Type</div>
|
||||
</th>
|
||||
<th className="d-none d-sm-table-cell">
|
||||
<div className="text-start ms-5">Payment Mode</div>
|
||||
</th>
|
||||
<th className="d-none d-sm-table-cell">Paid By</th>
|
||||
<th className="d-none d-md-table-cell">Amount</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
className="card-body table border-top dataTable no-footer dtr-column text-nowrap"
|
||||
aria-describedby="DataTables_Table_0_info"
|
||||
id="horizontal-example"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="d-none d-sm-table-cell">
|
||||
<div className="text-start ms-5">Expense Type</div>
|
||||
</th>
|
||||
<th className="d-none d-sm-table-cell">
|
||||
<div className="text-start ms-5">Payment Mode</div>
|
||||
</th>
|
||||
<th className="d-none d-sm-table-cell">Submitted By</th>
|
||||
<th className="d-none d-sm-table-cell">Submitted</th>
|
||||
<th className="d-none d-md-table-cell">Amount</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{[...Array(groups)].map((_, groupIdx) => (
|
||||
<React.Fragment key={`group-${groupIdx}`}>
|
||||
{/* Fake Date Group Header Row */}
|
||||
<tr className="bg-light">
|
||||
<td colSpan={8}>
|
||||
<SkeletonCell width="150px" height={20} />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* Rows under this group */}
|
||||
{[...Array(rowsPerGroup)].map((__, rowIdx) => (
|
||||
<tr key={`row-${groupIdx}-${rowIdx}`} className={rowIdx % 2 === 0 ? "odd" : "even"}>
|
||||
{/* Expense Type */}
|
||||
<td className="text-start d-none d-sm-table-cell ms-5">
|
||||
<SkeletonCell width="90px" height={16} />
|
||||
</td>
|
||||
|
||||
{/* Payment Mode */}
|
||||
<td className="text-start d-none d-sm-table-cell ms-5">
|
||||
<SkeletonCell width="90px" height={16} />
|
||||
</td>
|
||||
|
||||
{/* Paid By (Avatar + name) */}
|
||||
<td className="text-start d-none d-sm-table-cell ms-5">
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<SkeletonCell width="30px" height={30} className="rounded-circle" />
|
||||
<SkeletonCell width="80px" height={16} />
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Amount */}
|
||||
<td className="d-none d-md-table-cell text-end">
|
||||
<SkeletonCell width="60px" height={16} />
|
||||
</td>
|
||||
|
||||
{/* Status */}
|
||||
<td>
|
||||
<SkeletonCell width="80px" height={22} className="rounded" />
|
||||
</td>
|
||||
|
||||
{/* Action */}
|
||||
<td>
|
||||
<div className="d-flex justify-content-center align-items-center gap-2">
|
||||
{[...Array(3)].map((__, i) => (
|
||||
<SkeletonCell
|
||||
key={i}
|
||||
width={20}
|
||||
height={20}
|
||||
className="rounded"
|
||||
style={{ display: "inline-block" }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<tbody>
|
||||
{[...Array(groups)].map((_, groupIdx) => (
|
||||
<React.Fragment key={`group-${groupIdx}`}>
|
||||
{/* Fake Date Group Header Row */}
|
||||
<tr className="bg-light">
|
||||
<td colSpan={8}>
|
||||
<SkeletonCell width="150px" height={20} />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Rows under this group */}
|
||||
{[...Array(rowsPerGroup)].map((__, rowIdx) => (
|
||||
<tr
|
||||
key={`row-${groupIdx}-${rowIdx}`}
|
||||
className={rowIdx % 2 === 0 ? "odd" : "even"}
|
||||
>
|
||||
{/* Expense Type */}
|
||||
<td className="text-start d-none d-sm-table-cell ms-5">
|
||||
<SkeletonCell width="90px" height={16} />
|
||||
</td>
|
||||
|
||||
{/* Payment Mode */}
|
||||
<td className="text-start d-none d-sm-table-cell ms-5">
|
||||
<SkeletonCell width="90px" height={16} />
|
||||
</td>
|
||||
|
||||
{/* Submitted By (Avatar + name) */}
|
||||
<td className="text-start d-none d-sm-table-cell ms-5">
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<SkeletonCell
|
||||
width="30px"
|
||||
height={30}
|
||||
className="rounded-circle"
|
||||
/>
|
||||
<SkeletonCell width="80px" height={16} />
|
||||
</div>
|
||||
</td>
|
||||
{/* Submitted */}
|
||||
<td className="d-none d-md-table-cell text-end">
|
||||
<SkeletonCell width="70px" height={16} />
|
||||
</td>
|
||||
|
||||
{/* Amount */}
|
||||
<td className="d-none d-md-table-cell text-end">
|
||||
<SkeletonCell width="60px" height={16} />
|
||||
</td>
|
||||
|
||||
{/* Status */}
|
||||
<td>
|
||||
<SkeletonCell
|
||||
width="80px"
|
||||
height={22}
|
||||
className="rounded"
|
||||
/>
|
||||
</td>
|
||||
|
||||
{/* Action */}
|
||||
<td>
|
||||
<div className="d-flex justify-content-center align-items-center gap-2">
|
||||
{[...Array(3)].map((__, i) => (
|
||||
<SkeletonCell
|
||||
key={i}
|
||||
width={20}
|
||||
height={20}
|
||||
className="rounded"
|
||||
style={{ display: "inline-block" }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export const ExpenseFilterSkeleton = () => {
|
||||
return (
|
||||
<div className="p-3 text-start">
|
||||
|
@ -5,7 +5,10 @@ import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||
const ExpenseStatusLogs = ({ data }) => {
|
||||
const [visibleCount, setVisibleCount] = useState(4);
|
||||
|
||||
const logsToShow = data?.expenseLogs?.slice(0, visibleCount) || [];
|
||||
const logsToShow = [...(data?.expenseLogs || [])]
|
||||
.sort((a, b) => new Date(b.updateAt) - new Date(a.updateAt))
|
||||
.slice(0, visibleCount);
|
||||
|
||||
|
||||
const handleShowMore = () => {
|
||||
setVisibleCount((prev) => prev + 4);
|
||||
|
@ -9,7 +9,7 @@ import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { defaultActionValues, ExpenseActionScheam } from "./ExpenseSchema";
|
||||
import { useExpenseContext } from "../../pages/Expense/ExpensePage";
|
||||
import { getColorNameFromHex } from "../../utils/appUtils";
|
||||
import { getColorNameFromHex, getIconByFileType } from "../../utils/appUtils";
|
||||
import { ExpenseDetailsSkeleton } from "./ExpenseSkeleton";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import {
|
||||
@ -274,34 +274,21 @@ const ViewExpense = ({ ExpenseId }) => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="col-12 text-start">
|
||||
<label className="form-label me-2 mb-0 fw-semibold">Attachment :</label>
|
||||
<div className="col-12 text-start">
|
||||
<label className="form-label me-2 mb-2 fw-semibold">Attachment :</label>
|
||||
|
||||
{data?.documents?.map((doc) => {
|
||||
const getIconByType = (type) => {
|
||||
if (!type) return "bx bx-file";
|
||||
<div className="d-flex flex-wrap gap-2">
|
||||
{data?.documents?.map((doc) => {
|
||||
const isImage = doc.contentType?.includes("image");
|
||||
|
||||
if (type.includes("pdf")) return "bxs-file-pdf";
|
||||
if (type.includes("word")) return "bxs-file-doc";
|
||||
if (type.includes("excel") || type.includes("spreadsheet"))
|
||||
return "bxs-file-xls";
|
||||
if (type.includes("image")) return "bxs-file-image";
|
||||
if (type.includes("zip") || type.includes("rar"))
|
||||
return "bxs-file-archive";
|
||||
|
||||
return "bx bx-file";
|
||||
};
|
||||
|
||||
const isImage = doc.contentType?.includes("image");
|
||||
|
||||
return (
|
||||
<div
|
||||
className="list-group-item list-group-item-action d-flex align-items-center"
|
||||
key={doc.documentId}
|
||||
>
|
||||
return (
|
||||
<div
|
||||
className="rounded me-1 d-flex align-items-center justify-content-center cursor-pointer"
|
||||
style={{ height: "50px", width: "60px", position: "relative" }}
|
||||
key={doc.documentId}
|
||||
className="border rounded hover-scale p-2 d-flex flex-column align-items-center"
|
||||
style={{
|
||||
width: "80px",
|
||||
cursor: isImage ? "pointer" : "default",
|
||||
}}
|
||||
onClick={() => {
|
||||
if (isImage) {
|
||||
setDocumentView({
|
||||
@ -312,31 +299,22 @@ const ViewExpense = ({ ExpenseId }) => {
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className={`bx ${getIconByType(
|
||||
doc.contentType
|
||||
)} text-primary`}
|
||||
style={{ fontSize: "35px" }}
|
||||
className={`bx ${getIconByFileType(doc.contentType)}`}
|
||||
style={{ fontSize: "30px" }}
|
||||
></i>
|
||||
<small
|
||||
className="text-center text-tiny text-truncate w-100"
|
||||
title={doc.fileName}
|
||||
>
|
||||
{doc.fileName}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div className="w-100">
|
||||
<small className="mb-0 small">{doc.fileName}</small>
|
||||
<div className="d">
|
||||
<a
|
||||
href={doc.preSignedUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="bx bx-cloud-download cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{data.expensesReimburse && (
|
||||
{data.expensesReimburse && (
|
||||
<div className="row text-start">
|
||||
<div className="col-md-6 mb-sm-0 mb-2">
|
||||
<label className="form-label me-2 mb-0 fw-semibold">
|
||||
@ -371,7 +349,7 @@ const ViewExpense = ({ ExpenseId }) => {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<hr className="divider my-1" />
|
||||
<hr className="divider my-1 py-3 divider-primary" />
|
||||
|
||||
{Array.isArray(data?.nextStatus) && data.nextStatus.length > 0 && (
|
||||
<>
|
||||
@ -457,11 +435,8 @@ const ViewExpense = ({ ExpenseId }) => {
|
||||
</>
|
||||
)}
|
||||
|
||||
<ExpenseStatusLogs data={data}/>
|
||||
|
||||
<ExpenseStatusLogs data={data} />
|
||||
</form>
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,14 @@
|
||||
// it important ------
|
||||
export const mastersList = [ {id: 1, name: "Application Role"}, {id: 2, name: "Job Role"}, {id: 3, name: "Activity"},{id: 4, name:"Work Category"},{id:5,name:"Contact Category"},{id:6,name:"Contact Tag"}]
|
||||
export const mastersList = [
|
||||
{ id: 1, name: "Application Role" },
|
||||
{ id: 2, name: "Job Role" },
|
||||
{ id: 3, name: "Activity" },
|
||||
{ id: 4, name: "Work Category" },
|
||||
{ id: 5, name: "Contact Category" },
|
||||
{ id: 6, name: "Contact Tag" },
|
||||
{ id: 7, name: "Expense Type" },
|
||||
{ id: 8, name: "Payment Mode" },
|
||||
];
|
||||
// -------------------
|
||||
|
||||
export const dailyTask = [
|
||||
|
@ -180,7 +180,7 @@ const ExpensePage = () => {
|
||||
{ViewDocument.IsOpen && (
|
||||
<GlobalModel
|
||||
isOpen
|
||||
size="lg"
|
||||
size="md"
|
||||
key={ViewDocument.Image ?? "doc"}
|
||||
closeModal={() => setDocumentView({ IsOpen: false, Image: null })}
|
||||
>
|
||||
|
@ -9,13 +9,18 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => {
|
||||
const selectedMaster = useSelector(
|
||||
(store) => store.localVariables.selectedMaster
|
||||
);
|
||||
const hiddenColumns = [
|
||||
const hiddenColumns = [
|
||||
"id",
|
||||
"featurePermission",
|
||||
"tenant",
|
||||
"tenantId",
|
||||
"checkLists",
|
||||
"isSystem",
|
||||
"isActive",
|
||||
"noOfPersonsRequired",
|
||||
"color",
|
||||
"displayName",
|
||||
"permissionIds"
|
||||
];
|
||||
|
||||
const safeData = Array.isArray(data) ? data : [];
|
||||
|
@ -47,3 +47,17 @@ export const useDebounce = (value, delay = 500) => {
|
||||
|
||||
return debouncedValue;
|
||||
};
|
||||
|
||||
export const getIconByFileType = (type = "") => {
|
||||
const lower = type.toLowerCase();
|
||||
|
||||
if (lower === "application/pdf") return "bxs-file-pdf";
|
||||
if (lower.includes("word")) return "bxs-file-doc";
|
||||
if (lower.includes("excel") || lower.includes("spreadsheet"))
|
||||
return "bxs-file-xls";
|
||||
if (lower === "image/png") return "bxs-file-png";
|
||||
if (lower === "image/jpeg" || lower === "image/jpg") return "bxs-file-jpg";
|
||||
if (lower.includes("zip") || lower.includes("rar")) return "bxs-file-archive";
|
||||
|
||||
return "bx bx-file";
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user