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
13 changes: 10 additions & 3 deletions src/components/BMDashboard/ItemList/ItemListView.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@

.selectInput {
display: grid;
grid-template-columns: auto 1fr auto 1fr auto 1fr;
grid-template-columns: auto 1fr auto 1fr;
align-items: center;
gap: 15px;
width: 100%;
min-width: 400px;
max-width: 1200px;
margin: 0 auto 10px auto;
overflow: visible;
Expand All @@ -62,14 +63,20 @@
.selectInput select {
height: 38px;
width: 100%;
min-width: 220px;
max-width: 240px;
min-width: 400px;
max-width: 1200px;
padding: 5px;
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 8px;
}

.selectInput :global(.react-select__control) {
width: 100%;
max-width: 1200px;
min-width: 400px;
}

.selectInput input[type='text'] {
padding: 5px;
margin-bottom: 8px;
Expand Down
78 changes: 48 additions & 30 deletions src/components/BMDashboard/ItemList/SelectForm.jsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,61 @@
import { Form, FormGroup, Label, Input } from 'reactstrap';
import { useEffect, useMemo, useState } from 'react';
import { Form, FormGroup, Label } from 'reactstrap';
import Select from 'react-select';
import styles from './ItemListView.module.css';

const PROJECT_KEY = 'tool_selected_projects';

export default function SelectForm({ items, setSelectedProject, setSelectedItem }) {
let projectsSet = [];
if (items.length) {
projectsSet = [...new Set(items.map(el => el.project?.name))];
}
const [selectedProjects, setSelectedProjects] = useState([]);

// Build project list
const projectOptions = useMemo(() => {
if (!items?.length) return [];

Check warning on line 13 in src/components/BMDashboard/ItemList/SelectForm.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'items.length' is missing in props validation

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZsFmLvVdRj22FcUIIgb&open=AZsFmLvVdRj22FcUIIgb&pullRequest=4537
const unique = [...new Set(items.map(i => i.project?.name).filter(Boolean))];

Check warning on line 14 in src/components/BMDashboard/ItemList/SelectForm.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'items.map' is missing in props validation

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZsFmLvVdRj22FcUIIgc&open=AZsFmLvVdRj22FcUIIgc&pullRequest=4537
return unique.map(name => ({
label: name,
value: name,
}));
}, [items]);

// Restore saved values
useEffect(() => {
const saved = JSON.parse(localStorage.getItem(PROJECT_KEY));

if (Array.isArray(saved)) {
setSelectedProjects(saved);
setSelectedProject(saved.map(p => p.value));
}
}, []);

const handleChange = event => {
const handleChange = selected => {
const values = selected || [];

setSelectedProjects(values);
setSelectedItem('all');
setSelectedProject(event.target.value);
setSelectedProject(values.length ? values.map(v => v.value) : 'all');

localStorage.setItem(PROJECT_KEY, JSON.stringify(values));
};

return (
<Form>
<FormGroup className={`${styles.selectInput}`}>
<Label htmlFor="select-project">Project:</Label>
<Input
id="select-project"
name="select-project"
type="select"
<FormGroup className={styles.selectInput}>
<Label>Project:</Label>

<Select
isMulti
isSearchable
isClearable
options={projectOptions}
value={selectedProjects}
onChange={handleChange}
disabled={!items.length}
>
{items.length ? (
<>
<option value="all">All</option>
{projectsSet.map(name => {
return (
<option key={name} value={name}>
{name}
</option>
);
})}
</>
) : (
<option>No data</option>
)}
</Input>
isDisabled={!items?.length}

Check warning on line 53 in src/components/BMDashboard/ItemList/SelectForm.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'items.length' is missing in props validation

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZsFmLvVdRj22FcUIIgd&open=AZsFmLvVdRj22FcUIIgd&pullRequest=4537
placeholder="Search or select projects..."
classNamePrefix="react-select"
/>

<small className={styles.helperText}>Select one or more projects to filter results.</small>
</FormGroup>
</Form>
);
Expand Down
117 changes: 73 additions & 44 deletions src/components/BMDashboard/ItemList/SelectItem.jsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,92 @@
import { Form, FormGroup, Label, Input } from 'reactstrap';
import { useEffect, useMemo, useState } from 'react';
import { Form, FormGroup, Label } from 'reactstrap';
import Select from 'react-select';
import styles from './ItemListView.module.css';

const ITEM_KEY = 'tool_selected_items';

export default function SelectItem({
items,
selectedProject,
selectedItem,
setSelectedItem,
label,
}) {
let itemSet = [];
if (items?.length) {
if (selectedProject === 'all') {
itemSet = [
...new Set(
items
.filter(m => m.itemType?.name) // Filter out items with null/undefined names
.map(m => m.itemType.name),
),
];
const [localValues, setLocalValues] = useState([]);

// ✅ Build filtered tool options
const itemOptions = useMemo(() => {
if (!items?.length) return [];

Check warning on line 19 in src/components/BMDashboard/ItemList/SelectItem.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'items.length' is missing in props validation

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZsFmL44dRj22FcUIIge&open=AZsFmL44dRj22FcUIIge&pullRequest=4537

let list = items;

Check warning on line 21 in src/components/BMDashboard/ItemList/SelectItem.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this useless assignment to variable "list".

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZsFmL44dRj22FcUIIgf&open=AZsFmL44dRj22FcUIIgf&pullRequest=4537

if (Array.isArray(selectedProject)) {
list = items.filter(i => selectedProject.includes(i.project?.name) && i.itemType?.name);

Check warning on line 24 in src/components/BMDashboard/ItemList/SelectItem.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'items.filter' is missing in props validation

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZsFmL44dRj22FcUIIgg&open=AZsFmL44dRj22FcUIIgg&pullRequest=4537

Check warning on line 24 in src/components/BMDashboard/ItemList/SelectItem.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'selectedProject.includes' is missing in props validation

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZsFmL44dRj22FcUIIgh&open=AZsFmL44dRj22FcUIIgh&pullRequest=4537
} else if (selectedProject !== 'all') {

Check warning on line 25 in src/components/BMDashboard/ItemList/SelectItem.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZsFmL44dRj22FcUIIgi&open=AZsFmL44dRj22FcUIIgi&pullRequest=4537
list = items.filter(i => i.project?.name === selectedProject && i.itemType?.name);

Check warning on line 26 in src/components/BMDashboard/ItemList/SelectItem.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'items.filter' is missing in props validation

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZsFmL44dRj22FcUIIgj&open=AZsFmL44dRj22FcUIIgj&pullRequest=4537
} else {
itemSet = [
...new Set(
items
.filter(mat => mat.project?.name === selectedProject && mat.itemType?.name)
.map(m => m.itemType.name),
),
];
list = items.filter(i => i.itemType?.name);

Check warning on line 28 in src/components/BMDashboard/ItemList/SelectItem.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'items.filter' is missing in props validation

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZsFmL44dRj22FcUIIgk&open=AZsFmL44dRj22FcUIIgk&pullRequest=4537
}

const names = [...new Set(list.map(i => i.itemType.name))];

return names.map(name => ({
label: name,
value: name,
}));
}, [items, selectedProject]);

// ✅ Restore saved selections
useEffect(() => {
const saved = JSON.parse(localStorage.getItem(ITEM_KEY));

if (Array.isArray(saved)) {
setLocalValues(saved);
setSelectedItem(saved.map(s => s.value));
}
}
}, []);

// ✅ Sync reset from parent
useEffect(() => {
const isMulti = Array.isArray(selectedItem);

if (selectedItem === 'all' || (isMulti && selectedItem.length === 0)) {

Check warning on line 53 in src/components/BMDashboard/ItemList/SelectItem.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'selectedItem.length' is missing in props validation

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZsFmL44dRj22FcUIIgl&open=AZsFmL44dRj22FcUIIgl&pullRequest=4537
setLocalValues([]);
}
}, [selectedItem]);

const handleChange = selected => {
const values = selected || [];

setLocalValues(values);
setSelectedItem(values.length ? values.map(v => v.value) : 'all');

localStorage.setItem(ITEM_KEY, JSON.stringify(values));
};

return (
<Form>
<FormGroup className={`${styles.selectInput}`}>
<Label htmlFor="select-material" style={{ marginLeft: '10px' }}>
<FormGroup className={styles.selectInput}>
<Label htmlFor="select-item" style={{ marginLeft: '10px' }}>
{label ? `${label}:` : 'Material:'}
</Label>
<Input
id="select-item"
name="select-item"
type="select"
value={selectedItem}
onChange={e => setSelectedItem(e.target.value)}
disabled={!items.length}
>
{items.length ? (
<>
<option value="all" key="all-option">
All
</option>
{itemSet.map(itemName => (
<option key={`item-${itemName}`} value={itemName}>
{itemName}
</option>
))}
</>
) : (
<option key="no-data">No data</option>
)}
</Input>

<Select
inputId="select-item"
isMulti
isSearchable
isClearable
options={itemOptions}
value={localValues}
onChange={handleChange}
isDisabled={!items?.length}

Check warning on line 82 in src/components/BMDashboard/ItemList/SelectItem.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'items.length' is missing in props validation

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZsFmL44dRj22FcUIIgm&open=AZsFmL44dRj22FcUIIgm&pullRequest=4537
placeholder="Search or select..."
classNamePrefix="react-select"
/>

<small className={styles.helperText}>
Search and select one or more tools to filter results.
</small>
</FormGroup>
</Form>
);
Expand Down
56 changes: 37 additions & 19 deletions src/components/BMDashboard/ToolItemList/ToolItemListView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,49 +13,65 @@ export function ToolItemListView({
UpdateItemModal,
dynamicColumns,
}) {
const [filteredItems, setFilteredItems] = useState(items);
const [filteredItems, setFilteredItems] = useState([]);
const [selectedProject, setSelectedProject] = useState('all');
const [selectedItem, setSelectedItem] = useState('all');
const [isError, setIsError] = useState(false);

// Load initial items
useEffect(() => {
if (items) setFilteredItems([...items]);
if (Array.isArray(items)) {
setFilteredItems([...items]);
}
}, [items]);

// ✅ FULL multi-select compatible filtering
useEffect(() => {
let filterItems;
if (!items) return;
if (selectedProject === 'all' && selectedItem === 'all') {
setFilteredItems([...items]);
} else if (selectedProject !== 'all' && selectedItem === 'all') {
filterItems = items.filter(item => item.project?.name === selectedProject);
setFilteredItems([...filterItems]);
} else if (selectedProject === 'all' && selectedItem !== 'all') {
filterItems = items.filter(item => item.itemType?.name === selectedItem);
setFilteredItems([...filterItems]);
} else {
filterItems = items.filter(
item => item.project?.name === selectedProject && item.itemType?.name === selectedItem,
);
setFilteredItems([...filterItems]);
if (!Array.isArray(items)) return;

const projectIsMulti = Array.isArray(selectedProject);
const itemIsMulti = Array.isArray(selectedItem);

const hasProjects = projectIsMulti && selectedProject.length > 0;
const hasItems = itemIsMulti && selectedItem.length > 0;

let result = [...items];

// ✅ Project filter (single + multi)
if (hasProjects) {
result = result.filter(item => selectedProject.includes(item.project?.name));
} else if (!projectIsMulti && selectedProject !== 'all') {
result = result.filter(item => item.project?.name === selectedProject);
}

// ✅ Item / Tool filter (single + multi)
if (hasItems) {
result = result.filter(item => selectedItem.includes(item.itemType?.name));
} else if (!itemIsMulti && selectedItem !== 'all') {
result = result.filter(item => item.itemType?.name === selectedItem);
}

setFilteredItems(result);
}, [selectedProject, selectedItem, items]);

// Error handling
useEffect(() => {
setIsError(Object.entries(errors).length > 0);
}, [errors]);

if (isError) {
return (
<main className={`${styles.itemsListContainer}`}>
<main className={styles.itemsListContainer}>
<h2>{itemType} List</h2>
<BMError errors={errors} />
</main>
);
}

return (
<main className={`${styles.itemsListContainer}`}>
<main className={styles.itemsListContainer}>
<h3>{itemType}</h3>

<section>
<span style={{ display: 'flex', margin: '5px' }}>
{items && (
Expand All @@ -65,6 +81,7 @@ export function ToolItemListView({
setSelectedProject={setSelectedProject}
setSelectedItem={setSelectedItem}
/>

<SelectItem
items={items}
selectedProject={selectedProject}
Expand All @@ -75,6 +92,7 @@ export function ToolItemListView({
</>
)}
</span>

{filteredItems && (
<ToolItemsTable
selectedProject={selectedProject}
Expand Down
Loading