305 lines
8.9 KiB
JavaScript
305 lines
8.9 KiB
JavaScript
import React from "react";
|
|
|
|
const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => (
|
|
<div
|
|
className={`skeleton mb-2 ${className}`}
|
|
style={{
|
|
height,
|
|
width,
|
|
}}
|
|
></div>
|
|
);
|
|
|
|
const ExpenseSkeleton = () => {
|
|
return (
|
|
<div className="container p-3">
|
|
<div className="d-flex justify-content-center">
|
|
<SkeletonLine height={20} width="200px" />
|
|
</div>
|
|
|
|
{[...Array(5)].map((_, idx) => (
|
|
<div className="row my-2" key={idx}>
|
|
<div className="col-md-6">
|
|
<SkeletonLine />
|
|
</div>
|
|
<div className="col-md-6">
|
|
<SkeletonLine />
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
<div className="row my-2">
|
|
<div className="col-md-12">
|
|
<SkeletonLine height={60} />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="row my-2">
|
|
<div className="col-md-12">
|
|
<SkeletonLine height={120} />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="d-flex justify-content-center gap-2 mt-3">
|
|
<SkeletonLine height={35} width="100px" />
|
|
<SkeletonLine height={35} width="100px" />
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ExpenseSkeleton;
|
|
|
|
export const ExpenseDetailsSkeleton = () => {
|
|
return (
|
|
<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" />
|
|
</div>
|
|
|
|
{[...Array(3)].map((_, i) => (
|
|
<div className="col-12 col-md-4 mb-3" key={`row-1-${i}`}>
|
|
<SkeletonLine height={14} className="mb-1" />
|
|
<SkeletonLine />
|
|
</div>
|
|
))}
|
|
|
|
{[...Array(6)].map((_, i) => (
|
|
<div className="col-12 col-md-4 mb-3" key={`row-2-${i}`}>
|
|
<SkeletonLine height={14} className="mb-1" />
|
|
<SkeletonLine />
|
|
</div>
|
|
))}
|
|
|
|
<div className="col-12 text-start">
|
|
<SkeletonLine height={14} width="150px" className="mb-1" />
|
|
|
|
<div className="d-flex flex-wrap gap-2">
|
|
{[...Array(2)].map((_, i) => (
|
|
<div
|
|
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>
|
|
|
|
<hr className="divider my-1" />
|
|
|
|
<div className="col-12 mb-3">
|
|
<SkeletonLine height={14} width="80px" className="mb-1" />
|
|
<SkeletonLine height={60} className="mb-2" />
|
|
<div className="d-flex gap-2 flex-wrap">
|
|
{[...Array(2)].map((_, i) => (
|
|
<SkeletonLine
|
|
key={i}
|
|
height={30}
|
|
width="100px"
|
|
className="rounded"
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
const SkeletonCell = ({
|
|
width = "100%",
|
|
height = 20,
|
|
className = "",
|
|
style = {},
|
|
}) => (
|
|
<div
|
|
className={`skeleton ${className}`}
|
|
style={{
|
|
width,
|
|
height,
|
|
borderRadius: 4,
|
|
...style,
|
|
}}
|
|
/>
|
|
);
|
|
|
|
export const ExpenseTableSkeleton = ({
|
|
groups = 3,
|
|
rowsPerGroup = 3,
|
|
headers,
|
|
}) => {
|
|
return (
|
|
<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>
|
|
{headers.map((header) => (
|
|
<th key={header} className="d-none d-sm-table-cell">
|
|
<div className="text-start ms-5">{header}</div>
|
|
</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>
|
|
|
|
{/* 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">
|
|
{/* Created Date Label and Skeleton */}
|
|
<div className="mb-3 w-100">
|
|
<SkeletonLine height={14} width="120px" className="mb-1" />
|
|
<SkeletonLine height={36} />
|
|
</div>
|
|
|
|
<div className="row g-2">
|
|
{/* Project Select */}
|
|
<div className="col-12 col-md-4 mb-3">
|
|
<SkeletonLine height={14} width="80px" className="mb-1" />
|
|
<SkeletonLine height={36} />
|
|
</div>
|
|
|
|
{/* Submitted By Select */}
|
|
<div className="col-12 col-md-4 mb-3">
|
|
<SkeletonLine height={14} width="100px" className="mb-1" />
|
|
<SkeletonLine height={36} />
|
|
</div>
|
|
|
|
{/* Paid By Select */}
|
|
<div className="col-12 col-md-4 mb-3">
|
|
<SkeletonLine height={14} width="70px" className="mb-1" />
|
|
<SkeletonLine height={36} />
|
|
</div>
|
|
|
|
{/* Status Checkboxes */}
|
|
<div className="col-12 mb-3">
|
|
<SkeletonLine height={14} width="80px" className="mb-2" />
|
|
<div className="d-flex flex-wrap">
|
|
{[...Array(3)].map((_, i) => (
|
|
<div className="d-flex align-items-center me-3 mb-2" key={i}>
|
|
<div
|
|
className="form-check-input bg-secondary me-2"
|
|
style={{
|
|
height: "16px",
|
|
width: "16px",
|
|
borderRadius: "3px",
|
|
}}
|
|
/>
|
|
<SkeletonLine height={14} width="60px" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Buttons */}
|
|
<div className="d-flex justify-content-end py-3 gap-2">
|
|
<SkeletonLine height={30} width="80px" className="rounded" />
|
|
<SkeletonLine height={30} width="80px" className="rounded" />
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|