Skip to content

Commit

Permalink
first stab at self service page
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffbliss committed Nov 13, 2024
1 parent 2624f9a commit 7e48928
Show file tree
Hide file tree
Showing 5 changed files with 319 additions and 1 deletion.
70 changes: 70 additions & 0 deletions src/components/DropDownSelector.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { useState } from 'react';

const DropDownSelector = (props) => {
const { availableSelections, selections, setSelections, option } = props;
const [isDropdownOpen, setIsDropdownOpen] = useState(false);

const handleAdd = (itemToAdd) => {
if (!selections.includes(itemToAdd)) {
setSelections([...selections, itemToAdd]);
}
setIsDropdownOpen(false);
};

const handleRemove = (itemToRemove) => {
setSelections(selections.filter(item => item !== itemToRemove));
};

return (
<div className="flex flex-col gap-2">
<div className="font-medium text-gray-700">{option}</div>

{/* Selected States */}
<div className="flex flex-col gap-2">
{selections.map((item) => (
<div
key={item}
className="flex items-center justify-between px-3 py-2 bg-amber-50 rounded"
>
<span>{item}</span>
<button
onClick={() => handleRemove(state)}
className="text-gray-500 hover:text-gray-700"
>
×
</button>
</div>
))}
</div>

{/* Add State Button & Dropdown */}
<div className="relative">
<button
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
className="flex items-center gap-2 px-3 py-2 text-blue-600 bg-blue-100 rounded hover:bg-blue-200"
>
<span className="text-lg">+</span>
Add another {option}
</button>

{isDropdownOpen && (
<div className="absolute z-10 w-full mt-1 bg-white border border-gray-200 rounded-md shadow-lg max-h-60 overflow-auto">
{availableSelections
.filter(item => !selections.includes(item))
.map((item) => (
<button
key={item}
onClick={() => handleAdd(item)}
className="w-full px-4 py-2 text-left hover:bg-gray-100"
>
{item}
</button>
))}
</div>
)}
</div>
</div>
);
};

export default DropDownSelector;
5 changes: 5 additions & 0 deletions src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import CommunityListPage from "./pages/CommunityListPage.jsx";
import CommunityPage from './pages/CommunityPage.jsx'
import PractitionerPage from "./pages/PractitionerPage.jsx";
import SelfServicePage from "./pages/SelfServicePage.jsx";

const router = createHashRouter([
{
Expand All @@ -21,6 +22,10 @@ const router = createHashRouter([
{
path: "/practitioner/:practitionerId",
element: <PractitionerPage />
},
{
path: "/selfservice",
element: <SelfServicePage />
}
])

Expand Down
2 changes: 1 addition & 1 deletion src/pages/CommunityPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export default function CommunityPage() {
>
<CommunityPane
community={ community }
></CommunityPane>
/>

{ /* Practitioners */ }

Expand Down
185 changes: 185 additions & 0 deletions src/pages/SelfServicePage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// React
import { useState, useEffect, useContext } from 'react';

// router
import { useParams } from 'react-router-dom';

// API
import { fetchCommunity, fetchPractitionersForCommunity, fetchOptionsFromAirtable } from '../util/api';

// components
import { CssBaseline, Stack, Container, Typography, Box } from '@mui/material';

import FullPageSpinner from '../components/FullPageSpinner';
import PractitionerPane from '../components/PractitionerPane';
import CommunityPane from '../components/CommunityPane';

import { ThemeProvider } from "@mui/material/styles";

// theme
import theme from '../theme';

import { RowHoverContext, SetHoverRowContext } from '../components/RowHoverContext';
import DropDownSelector from "../components/DropDownSelector.jsx";


export default function SelfServicePage() {

const [selectedOptions, setSelectedOptions] = useState({
state: [],
activities: [],
sectors: [],
hazards: [],
size: [],
});

const [availableOptions, setAvailableOptions] = useState({
state: [],
activities: [],
sectors: [],
hazards: [],
size: [],
});

const [ practitioners, setPractitioners ] = useState([]);
const [ poppedPractitioner, setPoppedPractitioner ] = useState(null);
const [ hoverRow, setHoverRow ] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);

const community = {
name: 'Self Service',
state: selectedOptions.state,
activities: selectedOptions.activities,
sectors: selectedOptions.sectors,
hazards: selectedOptions.hazards,
size: selectedOptions.size,
}

useEffect(() => {
const loadOptions = async () => {
setIsLoading(true);
await fetchOptionsFromAirtable(setAvailableOptions);
};

loadOptions().then(() => {
setIsLoading(false);
}).catch((err) => {
setError(err);
});
}, []);

if (error) {
return <div className="text-red-600 p-4">{error}</div>;
}

if (isLoading) {
return <div className="p-4">Loading options...</div>;
}

if (availableOptions) {

const practitionerPanes = practitioners.map((pract, index) => {
return <PractitionerPane
community={ community }
practitioner={ pract }
poppedPractitioner={ poppedPractitioner }
setPoppedPractitioner={ setPoppedPractitioner }
key={ index }
style={{
flex: 1
}}
></PractitionerPane>
})

return (
<ThemeProvider theme={theme}>
<RowHoverContext.Provider value={hoverRow}>
<SetHoverRowContext.Provider value={setHoverRow}>
<CssBaseline />
<Container maxWidth="xl" sx={{ p: 2 }} >
<Stack
direction='row'
gap={1}
ml={1}
sx={{
bgcolor: theme.palette.primary.lightGray,
}}
>
<CommunityPane
community={ community }
/>
<DropDownSelector
availableSelections = { availableOptions.state }
selections = { selectedOptions.state }
setSelections = { (selections) => setSelectedOptions({ ...selectedOptions, state: selections }) }
option = "State"
/>
<DropDownSelector
availableSelections = { availableOptions.activities }
selections = { selectedOptions.activities }
setSelections = { (selections) => setSelectedOptions({ ...selectedOptions, activities: selections }) }
option = "Activity"
/>
<DropDownSelector
availableSelections = { availableOptions.sectors }
selections = { selectedOptions.sectors }
setSelections = { (selections) => setSelectedOptions({ ...selectedOptions, sectors: selections }) }
option = "Sector"
/>
<DropDownSelector
availableSelections = { availableOptions.hazards }
selections = { selectedOptions.hazards }
setSelections = { (selections) => setSelectedOptions({ ...selectedOptions, hazards: selections }) }
option = "Hazard"
/>
<DropDownSelector
availableSelections = { availableOptions.size }
selections = { selectedOptions.size }
setSelections = { (selections) => setSelectedOptions({ ...selectedOptions, size: selections }) }
option = "Size"
/>

{ /* Practitioners */ }

<Stack sx={{ width: '60%' }}>
<Typography
color="primary.main"
sx={{
pt: 1,
height: '40px',
textAlign: 'center',
fontWeight: 700,
}}
variant="h5"
>
<Box
sx={{
display: {
xs: 'none',
md: 'inline-block',
},
}}
>Matched</Box> Practitioners
</Typography>
<Stack
direction='row'
gap={1}
mr={1}
>
{ practitionerPanes }
</Stack>
</Stack>
</Stack>
</Container>
</SetHoverRowContext.Provider>
</RowHoverContext.Provider>
</ThemeProvider>
)
} else {
return (
<FullPageSpinner></FullPageSpinner>
)
}

}
58 changes: 58 additions & 0 deletions src/util/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,61 @@ export const fetchPractitionersForCommunity = (communityId, setPractitioners) =>
})
})
}

export const fetchOptionsFromAirtable = (setOptions) => {
base('Options').select({
view: "Grid view",
fields: [
'State',
'Activities',
'Hazards',
'Size',
'Sectors'
]
}).firstPage(function(err, records) {
if (err) {
console.error(err);
return;
}

// Initialize our options object with Sets for unique values
const availableOptions = {
state: new Set(),
activities: new Set(),
hazards: new Set(),
size: new Set(),
sectors: new Set()
};

// Process records
records.forEach(record => {
const fields = record.fields;

// Add values to respective sets if they exist
if (fields.State) availableOptions.state.add(fields.State);
if (fields.Activities) {
(Array.isArray(fields.Activities) ? fields.Activities : [fields.Activities])
.forEach(activity => availableOptions.activities.add(activity));
}
if (fields.Hazards) {
(Array.isArray(fields.Hazards) ? fields.Hazards : [fields.Hazards])
.forEach(hazard => availableOptions.hazards.add(hazard));
}
if (fields.Size) availableOptions.size.add(fields.Size);
if (fields.Sectors) {
(Array.isArray(fields.Sectors) ? fields.Sectors : [fields.Sectors])
.forEach(sector => availableOptions.sectors.add(sector));
}
});

// Convert Sets to sorted arrays
const options = {
state: [...availableOptions.state].sort(),
activities: [...availableOptions.activities].sort(),
hazards: [...availableOptions.hazards].sort(),
size: [...availableOptions.size].sort(),
sectors: [...availableOptions.sectors].sort()
};
setOptions(options);
});
};

0 comments on commit 7e48928

Please sign in to comment.