Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/BMDashboard/ItemList/ItemListView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ export function ItemListView({
UpdateItemModal={UpdateItemModal}
dynamicColumns={dynamicColumns}
darkMode={darkMode}
itemType={itemType}
sortConfig={sortConfig}
onSort={handleSort}
totalItems={totalItems}
Expand Down
152 changes: 107 additions & 45 deletions src/components/BMDashboard/ItemList/ItemsTable.jsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
import { useState } from 'react';
import { Table, Button } from 'reactstrap';
import PropTypes from 'prop-types';
import { Table, Button, Badge } from 'reactstrap';
import { BiPencil } from 'react-icons/bi';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSortDown, faSort, faSortUp } from '@fortawesome/free-solid-svg-icons';
import RecordsModal from './RecordsModal';
import styles from './ItemListView.module.css';

const rowsPerPageOptions = [25, 50, 100];

function generatePageNumbers(current, total) {
if (total <= 7) {
return Array.from({ length: total }, (_, i) => i + 1);
}

if (current <= 3) {
return [1, 2, 3, 4, 5, '...', total];
}

if (current >= total - 2) {
return [1, '...', total - 4, total - 3, total - 2, total - 1, total];
}

if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);
if (current <= 3) return [1, 2, 3, 4, 5, '...', total];
if (current >= total - 2) return [1, '...', total - 4, total - 3, total - 2, total - 1, total];
return [1, '...', current - 1, current, current + 1, '...', total];
}

Expand All @@ -30,6 +23,7 @@ export default function ItemsTable({
UpdateItemModal,
dynamicColumns,
darkMode = false,
itemType,
sortConfig,
onSort,
totalItems,
Expand Down Expand Up @@ -60,9 +54,8 @@ export default function ItemsTable({
setRecordType(type);
};

const getNestedValue = (obj, path) => {
return path.split('.').reduce((acc, part) => (acc ? acc[part] : null), obj);
};
const getNestedValue = (obj, path) =>
path.split('.').reduce((acc, part) => (acc ? acc[part] : null), obj);

const getIconFor = key => {
if (!sortConfig?.key || sortConfig.key !== key) return faSort;
Expand All @@ -77,6 +70,18 @@ export default function ItemsTable({
Hold: 'hold',
};

const numericKeys = new Set(['stockBought', 'stockUsed', 'stockAvailable', 'stockWasted']);

const getColumnStyle = (key, isAction = false) => {
const base = { verticalAlign: 'middle' };
if (numericKeys.has(key)) base.textAlign = 'right';
if (isAction) {
base.borderLeft = '2px solid #dee2e6';
base.textAlign = 'center';
}
return base;
};

return (
<>
<RecordsModal
Expand All @@ -93,47 +98,81 @@ export default function ItemsTable({
<Table className={darkMode ? styles.darkTable : ''}>
<thead className={styles.stickyThead}>
<tr>
<th onClick={() => onSort?.('project')} className={styles.sortableTh}>
<th
onClick={() => onSort?.('project')}
className={styles.sortableTh}
style={{ verticalAlign: 'middle' }}
>
Project <FontAwesomeIcon icon={getIconFor('project')} size="lg" />
</th>

<th onClick={() => onSort?.('name')} className={styles.sortableTh}>
<th
onClick={() => onSort?.('name')}
className={styles.sortableTh}
style={{ verticalAlign: 'middle' }}
>
Name <FontAwesomeIcon icon={getIconFor('name')} size="lg" />
</th>

{dynamicColumns.map(({ label }) => {
{dynamicColumns.map(({ label, key }) => {
const sortKey = dynamicSortKeyByLabel[label];
const clickable = Boolean(sortKey);

return (
<th
key={label}
onClick={clickable ? () => onSort?.(sortKey) : undefined}
className={clickable ? styles.sortableTh : undefined}
style={getColumnStyle(key)}
>
{label} {clickable && <FontAwesomeIcon icon={getIconFor(sortKey)} size="lg" />}
</th>
);
})}

<th>Usage Record</th>
<th>Updates</th>
<th>Purchases</th>
<th style={getColumnStyle(null, true)} title="View usage history and charts">
Usage Record
</th>
<th
style={{ verticalAlign: 'middle', textAlign: 'center' }}
title="View history of manual updates"
>
Updates
</th>
<th
style={{ verticalAlign: 'middle', textAlign: 'center' }}
title="View procurement history"
>
Purchases
</th>
</tr>
</thead>

<tbody>
{filteredItems && filteredItems.length > 0 ? (
filteredItems.map(el => (
<tr key={el._id}>
<td>{el.project?.name}</td>
<td>{el.itemType?.name}</td>

{dynamicColumns.map(({ label, key }) => (
<td key={label}>{getNestedValue(el, key)}</td>
))}

<td className={`${styles.itemsCell}`}>
<td style={{ verticalAlign: 'middle' }}>{el.project?.name}</td>
<td style={{ verticalAlign: 'middle' }}>{el.itemType?.name}</td>
{dynamicColumns.map(({ label, key }) => {
const value = getNestedValue(el, key);
if (key === 'stockAvailable' && Number(value) < 10) {
return (
<td key={label} style={getColumnStyle(key)}>
<Badge
color="danger"
pill
className="me-2"
style={{ marginRight: '8px' }}
>
Low
</Badge>
{value}
</td>
);
}
return (
<td key={label} style={getColumnStyle(key)}>
{value}
</td>
);
})}
<td className={styles.itemsCell} style={getColumnStyle(null, true)}>
<button
type="button"
onClick={() => handleEditRecordsClick(el, 'UsageRecord')}
Expand All @@ -150,8 +189,10 @@ export default function ItemsTable({
View
</Button>
</td>

<td className={`${styles.itemsCell}`}>
<td
className={styles.itemsCell}
style={{ verticalAlign: 'middle', textAlign: 'center' }}
>
<button
type="button"
onClick={() => handleEditRecordsClick(el, 'Update')}
Expand All @@ -168,8 +209,7 @@ export default function ItemsTable({
View
</Button>
</td>

<td>
<td style={{ verticalAlign: 'middle', textAlign: 'center' }}>
<Button
color="primary"
outline
Expand Down Expand Up @@ -206,24 +246,20 @@ export default function ItemsTable({
))}
</select>
</div>

<div className={styles.rangeInfo}>
{startRow}-{endRow} of {totalItems}
</div>

<div className={styles.pageButtons}>
<button type="button" onClick={() => onPageChange?.(1)} disabled={currentPage === 1}>
{'<<'}
</button>

<button
type="button"
onClick={() => onPageChange?.(currentPage - 1)}
disabled={currentPage === 1}
>
{'<'}
</button>

{generatePageNumbers(currentPage, totalPages).map((p, idx) =>
typeof p === 'number' ? (
<button
Expand All @@ -241,15 +277,13 @@ export default function ItemsTable({
</span>
),
)}

<button
type="button"
onClick={() => onPageChange?.(currentPage + 1)}
disabled={currentPage === totalPages}
>
{'>'}
</button>

<button
type="button"
onClick={() => onPageChange?.(totalPages)}
Expand All @@ -262,3 +296,31 @@ export default function ItemsTable({
</>
);
}

ItemsTable.propTypes = {
selectedProject: PropTypes.string,
selectedItem: PropTypes.string,
filteredItems: PropTypes.arrayOf(PropTypes.object),
UpdateItemModal: PropTypes.elementType,
dynamicColumns: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string,
key: PropTypes.string,
}),
).isRequired,
darkMode: PropTypes.bool,
itemType: PropTypes.string,
sortConfig: PropTypes.shape({
key: PropTypes.string,
direction: PropTypes.string,
}),
onSort: PropTypes.func,
totalItems: PropTypes.number,
currentPage: PropTypes.number,
totalPages: PropTypes.number,
rowsPerPage: PropTypes.number,
startRow: PropTypes.number,
endRow: PropTypes.number,
onPageChange: PropTypes.func,
onRowsPerPageChange: PropTypes.func,
};
5 changes: 5 additions & 0 deletions src/components/BMDashboard/ItemList/RecordsModal.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,8 @@
color: #e6a800;
font-weight: bold;
}

.darkTable .stickyThead th {
background-color: #2f4157;
border-bottom: 1px solid #3f5269;
}
Loading
Loading