attachement display horizontically

This commit is contained in:
pramod mahajan 2025-08-06 10:22:47 +05:30
parent a87d3fb20a
commit 3d7a8a1c94
10 changed files with 287 additions and 171 deletions

View File

@ -28,6 +28,7 @@
<link rel="stylesheet" href="/assets/css/core-extend.css" /> <link rel="stylesheet" href="/assets/css/core-extend.css" />
<link rel="stylesheet" href="/assets/css/default.css" /> <link rel="stylesheet" href="/assets/css/default.css" />
<link rel="stylesheet" href="/assets/css/skeleton.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" /> <link rel="stylesheet" href="/assets/vendor/libs/perfect-scrollbar/perfect-scrollbar.css" />

View 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;
}

View File

@ -120,7 +120,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
firstName={e.createdBy?.firstName} firstName={e.createdBy?.firstName}
lastName={e.createdBy?.lastName} lastName={e.createdBy?.lastName}
/> />
<span> <span className="text-truncate">
{`${e.createdBy?.firstName ?? ""} ${ {`${e.createdBy?.firstName ?? ""} ${
e.createdBy?.lastName ?? "" e.createdBy?.lastName ?? ""
}`.trim() || "N/A"} }`.trim() || "N/A"}

View File

@ -1,6 +1,5 @@
import React from "react"; import React from "react";
const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => ( const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => (
<div <div
className={`skeleton mb-2 ${className}`} className={`skeleton mb-2 ${className}`}
@ -11,13 +10,12 @@ const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => (
></div> ></div>
); );
const ExpenseSkeleton = () => { const ExpenseSkeleton = () => {
return ( return (
<div className="container p-3"> <div className="container p-3">
<div className="d-flex justify-content-center"> <div className="d-flex justify-content-center">
<SkeletonLine height={20} width="200px" /> <SkeletonLine height={20} width="200px" />
</div> </div>
{[...Array(5)].map((_, idx) => ( {[...Array(5)].map((_, idx) => (
<div className="row my-2" key={idx}> <div className="row my-2" key={idx}>
@ -52,9 +50,6 @@ const ExpenseSkeleton = () => {
export default ExpenseSkeleton; export default ExpenseSkeleton;
export const ExpenseDetailsSkeleton = () => { export const ExpenseDetailsSkeleton = () => {
return ( return (
<div className="container px-3"> <div className="container px-3">
@ -77,31 +72,39 @@ export const ExpenseDetailsSkeleton = () => {
</div> </div>
))} ))}
<div className="col-12 text-start">
<SkeletonLine height={14} width="150px" className="mb-1" />
<div className="d-flex flex-wrap gap-2">
<div className="col-12 my-2">
<SkeletonLine height={14} width="100px" className="mb-2" />
{[...Array(2)].map((_, i) => ( {[...Array(2)].map((_, i) => (
<div <div
className="list-group-item d-flex align-items-center mb-2"
key={i} key={i}
className="border rounded p-2 d-flex flex-column align-items-center"
style={{ width: "80px" }}
> >
{/* Icon placeholder */}
<div <div
className="rounded me-2"
style={{ style={{
height: "50px", height: "30px",
width: "80px", width: "30px",
backgroundColor: "#dcdcdc",
borderRadius: "4px", borderRadius: "4px",
marginBottom: "6px",
}} }}
className="skeleton"
/>
{/* Filename placeholder */}
<div
style={{
height: "10px",
width: "100%",
}}
className="skeleton"
/> />
<div className="w-100">
<SkeletonLine height={14} width="60%" className="mb-1" />
<SkeletonLine height={14} width="20%" />
</div>
</div> </div>
))} ))}
</div> </div>
</div>
<hr className="divider my-1" /> <hr className="divider my-1" />
@ -123,7 +126,12 @@ export const ExpenseDetailsSkeleton = () => {
</div> </div>
); );
}; };
const SkeletonCell = ({ width = "100%", height = 20, className = "", style = {} }) => ( const SkeletonCell = ({
width = "100%",
height = 20,
className = "",
style = {},
}) => (
<div <div
className={`skeleton ${className}`} className={`skeleton ${className}`}
style={{ style={{
@ -151,7 +159,8 @@ export const ExpenseTableSkeleton = ({ groups = 3, rowsPerGroup = 3 }) => {
<th className="d-none d-sm-table-cell"> <th className="d-none d-sm-table-cell">
<div className="text-start ms-5">Payment Mode</div> <div className="text-start ms-5">Payment Mode</div>
</th> </th>
<th className="d-none d-sm-table-cell">Paid By</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 className="d-none d-md-table-cell">Amount</th>
<th>Status</th> <th>Status</th>
<th>Action</th> <th>Action</th>
@ -170,7 +179,10 @@ export const ExpenseTableSkeleton = ({ groups = 3, rowsPerGroup = 3 }) => {
{/* Rows under this group */} {/* Rows under this group */}
{[...Array(rowsPerGroup)].map((__, rowIdx) => ( {[...Array(rowsPerGroup)].map((__, rowIdx) => (
<tr key={`row-${groupIdx}-${rowIdx}`} className={rowIdx % 2 === 0 ? "odd" : "even"}> <tr
key={`row-${groupIdx}-${rowIdx}`}
className={rowIdx % 2 === 0 ? "odd" : "even"}
>
{/* Expense Type */} {/* Expense Type */}
<td className="text-start d-none d-sm-table-cell ms-5"> <td className="text-start d-none d-sm-table-cell ms-5">
<SkeletonCell width="90px" height={16} /> <SkeletonCell width="90px" height={16} />
@ -181,13 +193,21 @@ export const ExpenseTableSkeleton = ({ groups = 3, rowsPerGroup = 3 }) => {
<SkeletonCell width="90px" height={16} /> <SkeletonCell width="90px" height={16} />
</td> </td>
{/* Paid By (Avatar + name) */} {/* Submitted By (Avatar + name) */}
<td className="text-start d-none d-sm-table-cell ms-5"> <td className="text-start d-none d-sm-table-cell ms-5">
<div className="d-flex align-items-center gap-2"> <div className="d-flex align-items-center gap-2">
<SkeletonCell width="30px" height={30} className="rounded-circle" /> <SkeletonCell
width="30px"
height={30}
className="rounded-circle"
/>
<SkeletonCell width="80px" height={16} /> <SkeletonCell width="80px" height={16} />
</div> </div>
</td> </td>
{/* Submitted */}
<td className="d-none d-md-table-cell text-end">
<SkeletonCell width="70px" height={16} />
</td>
{/* Amount */} {/* Amount */}
<td className="d-none d-md-table-cell text-end"> <td className="d-none d-md-table-cell text-end">
@ -196,7 +216,11 @@ export const ExpenseTableSkeleton = ({ groups = 3, rowsPerGroup = 3 }) => {
{/* Status */} {/* Status */}
<td> <td>
<SkeletonCell width="80px" height={22} className="rounded" /> <SkeletonCell
width="80px"
height={22}
className="rounded"
/>
</td> </td>
{/* Action */} {/* Action */}
@ -223,7 +247,6 @@ export const ExpenseTableSkeleton = ({ groups = 3, rowsPerGroup = 3 }) => {
); );
}; };
export const ExpenseFilterSkeleton = () => { export const ExpenseFilterSkeleton = () => {
return ( return (
<div className="p-3 text-start"> <div className="p-3 text-start">

View File

@ -5,7 +5,10 @@ import { formatUTCToLocalTime } from "../../utils/dateUtils";
const ExpenseStatusLogs = ({ data }) => { const ExpenseStatusLogs = ({ data }) => {
const [visibleCount, setVisibleCount] = useState(4); 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 = () => { const handleShowMore = () => {
setVisibleCount((prev) => prev + 4); setVisibleCount((prev) => prev + 4);

View File

@ -9,7 +9,7 @@ import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; 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 { getColorNameFromHex } from "../../utils/appUtils"; import { getColorNameFromHex, getIconByFileType } from "../../utils/appUtils";
import { ExpenseDetailsSkeleton } from "./ExpenseSkeleton"; import { ExpenseDetailsSkeleton } from "./ExpenseSkeleton";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { import {
@ -275,33 +275,20 @@ const ViewExpense = ({ ExpenseId }) => {
</div> </div>
<div className="col-12 text-start"> <div className="col-12 text-start">
<label className="form-label me-2 mb-0 fw-semibold">Attachment :</label> <label className="form-label me-2 mb-2 fw-semibold">Attachment :</label>
<div className="d-flex flex-wrap gap-2">
{data?.documents?.map((doc) => { {data?.documents?.map((doc) => {
const getIconByType = (type) => {
if (!type) return "bx bx-file";
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"); const isImage = doc.contentType?.includes("image");
return ( return (
<div <div
className="list-group-item list-group-item-action d-flex align-items-center"
key={doc.documentId} key={doc.documentId}
> className="border rounded hover-scale p-2 d-flex flex-column align-items-center"
<div style={{
className="rounded me-1 d-flex align-items-center justify-content-center cursor-pointer" width: "80px",
style={{ height: "50px", width: "60px", position: "relative" }} cursor: isImage ? "pointer" : "default",
}}
onClick={() => { onClick={() => {
if (isImage) { if (isImage) {
setDocumentView({ setDocumentView({
@ -312,29 +299,20 @@ const ViewExpense = ({ ExpenseId }) => {
}} }}
> >
<i <i
className={`bx ${getIconByType( className={`bx ${getIconByFileType(doc.contentType)}`}
doc.contentType style={{ fontSize: "30px" }}
)} text-primary`}
style={{ fontSize: "35px" }}
></i> ></i>
</div> <small
className="text-center text-tiny text-truncate w-100"
<div className="w-100"> title={doc.fileName}
<small className="mb-0 small">{doc.fileName}</small> >
<div className="d"> {doc.fileName}
<a </small>
href={doc.preSignedUrl}
target="_blank"
rel="noopener noreferrer"
className="bx bx-cloud-download cursor-pointer"
/>
</div>
</div>
</div> </div>
); );
})} })}
</div> </div>
</div>
{data.expensesReimburse && ( {data.expensesReimburse && (
<div className="row text-start"> <div className="row text-start">
@ -371,7 +349,7 @@ const ViewExpense = ({ ExpenseId }) => {
)} )}
</div> </div>
)} )}
<hr className="divider my-1" /> <hr className="divider my-1 py-3 divider-primary" />
{Array.isArray(data?.nextStatus) && data.nextStatus.length > 0 && ( {Array.isArray(data?.nextStatus) && data.nextStatus.length > 0 && (
<> <>
@ -457,11 +435,8 @@ const ViewExpense = ({ ExpenseId }) => {
</> </>
)} )}
<ExpenseStatusLogs data={data}/> <ExpenseStatusLogs data={data} />
</form> </form>
); );
}; };

View File

@ -1,5 +1,14 @@
// it important ------ // 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 = [ export const dailyTask = [

View File

@ -180,7 +180,7 @@ const ExpensePage = () => {
{ViewDocument.IsOpen && ( {ViewDocument.IsOpen && (
<GlobalModel <GlobalModel
isOpen isOpen
size="lg" size="md"
key={ViewDocument.Image ?? "doc"} key={ViewDocument.Image ?? "doc"}
closeModal={() => setDocumentView({ IsOpen: false, Image: null })} closeModal={() => setDocumentView({ IsOpen: false, Image: null })}
> >

View File

@ -16,6 +16,11 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => {
"tenantId", "tenantId",
"checkLists", "checkLists",
"isSystem", "isSystem",
"isActive",
"noOfPersonsRequired",
"color",
"displayName",
"permissionIds"
]; ];
const safeData = Array.isArray(data) ? data : []; const safeData = Array.isArray(data) ? data : [];

View File

@ -47,3 +47,17 @@ export const useDebounce = (value, delay = 500) => {
return debouncedValue; 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";
};