Skip to content

Commit

Permalink
Merge pull request #25 from HackYourFuture-CPH/dashboard-navbar/Frontend
Browse files Browse the repository at this point in the history
Dashboard navbar/frontend
  • Loading branch information
MerajSharifi authored Apr 21, 2024
2 parents d91c844 + d1d7116 commit 270dd9e
Show file tree
Hide file tree
Showing 15 changed files with 597 additions and 359 deletions.
9 changes: 4 additions & 5 deletions packages/client/src/App.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Dashboard from './containers/Dashboard/Dashboard';
// TODO: put back comment after the login flow is down
// import { LandingPageContainer } from './containers/LandingPage/LandingPage';
import { PageNotFound } from './containers/PageNotFound/PageNotFound.Container';
import { LandingPageContainer } from './containers/LandingPage/LandingPage';
import { PageNotFound } from './containers/PageNotFound/PageNotFound';

Check warning on line 5 in packages/client/src/App.js

View workflow job for this annotation

GitHub Actions / build

'PageNotFound' is defined but never used
import { CheckinQuestions } from './containers/LandingPage/CheckinQuestionsPage/CheckinQuestions';
import { TeamIdContextProvider } from './hooks/contextHook';

Expand All @@ -14,10 +13,10 @@ function App() {
<Router>
<Routes>
{/* TODO: put back comment after the login flow is down */}
{/* <Route path="/" element={<LandingPageContainer />} /> */}
<Route path="/" element={<LandingPageContainer />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/questions" element={<CheckinQuestions />} />
<Route path="*" element={<PageNotFound />} />
{/* <Route path="*" element={<PageNotFound />} /> */}
</Routes>
</Router>
</TeamIdContextProvider>
Expand Down
68 changes: 68 additions & 0 deletions packages/client/src/components/Dashboard/EditingMember.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import { Typography, Button, TextField } from '@mui/material';
import PropTypes from 'prop-types';

const EditingMember = ({
editMemberId,
editMemberFirstName,
editMemberLastName,
setEditMemberFirstName,
setEditMemberLastName,
handleEditSave,
setEditMemberId,
}) => {
return (
<div className="edit-member-container">
<Typography variant="body1" className="edit-member-heading">
Edit Member
</Typography>
<div className="edit-member-details">
<div className="input-container">
<TextField
className="edit-member-input"
size="small"
id="outlined-basic"
label="First Name"
variant="outlined"
type="text"
value={editMemberFirstName}
onChange={(e) => setEditMemberFirstName(e.target.value)}
placeholder="First Name"
/>
<TextField
className="edit-member-input"
size="small"
id="outlined-basic"
label="Last Name"
variant="outlined"
type="text"
value={editMemberLastName}
onChange={(e) => setEditMemberLastName(e.target.value)}
placeholder="Last Name"
/>
</div>
<div className="button-container">
<Button variant="contained" onClick={handleEditSave}>
Save
</Button>
<Button variant="outlined" onClick={() => setEditMemberId(null)}>
Cancel
</Button>
</div>
</div>
</div>
);
};

// Define prop types for the EditingMember component
EditingMember.propTypes = {
editMemberId: PropTypes.number.isRequired,
editMemberFirstName: PropTypes.string.isRequired,
editMemberLastName: PropTypes.string.isRequired,
setEditMemberFirstName: PropTypes.func.isRequired,
setEditMemberLastName: PropTypes.func.isRequired,
handleEditSave: PropTypes.func.isRequired,
setEditMemberId: PropTypes.func.isRequired,
};

export default EditingMember;
48 changes: 48 additions & 0 deletions packages/client/src/components/Dashboard/NavigationBar.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
.menu-icon {
display: flex;
justify-content: center;
align-items: center;
gap: 18.204px;
flex: 1 0 0;
color: var(--fade-color);
}

.left-nav-bar-container {
display: flex;
padding: 40px 0px;
flex-direction: column;
align-items: center;
gap: 80px;
flex-shrink: 0;
border-radius: 0px 40px 40px 0px;
background: var(--primary-color);
}

.logo {
height: 4rem;
background: none;
}

.left-nav-bar {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
flex-shrink: 0;
flex-grow: 1;
}

.left-nav-bar-top {
display: flex;
flex-direction: column;
align-items: flex-start;
}

.left-nav-bar-top .menu-icon:nth-child(1) {
color: white;
}

.left-nav-bar-bottom {
display: flex;
align-items: center;
}
1 change: 1 addition & 0 deletions packages/client/src/components/Dashboard/NavigationBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import HelpOutlineOutlinedIcon from '@mui/icons-material/HelpOutlineOutlined';
import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined';
import ExitToAppOutlinedIcon from '@mui/icons-material/ExitToAppOutlined';
import logo from './logo-dark.svg';
import './NavigationBar.css';

const NavigationItem = ({ icon, name }) => (
<div style={{ marginBottom: '20px' }}>
Expand Down
42 changes: 42 additions & 0 deletions packages/client/src/components/Dashboard/TeamMemberListItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import PropTypes from 'prop-types';
import { IconButton } from '@mui/material';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete';
import Dashboard from '../../containers/Dashboard/Dashboard';

Check warning on line 6 in packages/client/src/components/Dashboard/TeamMemberListItem.jsx

View workflow job for this annotation

GitHub Actions / build

'Dashboard' is defined but never used

const TeamMemberListItem = ({
member,
handleEditMember,
handleDeleteMember,
}) => {
return (
<li key={member.member_id}>
<div className="member-info-container">
<div className="member-info">
{member.first_name} {member.last_name}
</div>
<div>
<IconButton onClick={() => handleEditMember(member.member_id)}>
<EditIcon />
</IconButton>
<IconButton onClick={() => handleDeleteMember(member.member_id)}>
<DeleteIcon />
</IconButton>
</div>
</div>
</li>
);
};

TeamMemberListItem.propTypes = {
member: PropTypes.shape({
member_id: PropTypes.number.isRequired,
first_name: PropTypes.string.isRequired,
last_name: PropTypes.string.isRequired,
}).isRequired,
handleEditMember: PropTypes.func.isRequired,
handleDeleteMember: PropTypes.func.isRequired,
};

export default TeamMemberListItem;
180 changes: 180 additions & 0 deletions packages/client/src/components/Dashboard/TeamMembers.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import React, { useEffect, useState } from 'react';
import { apiURL } from '../../apiURL';
import { AddTeamMemberModal } from '../../containers/LandingPage/AddTeamMemberModal';
import { Typography, Button } from '@mui/material';
import TeamMemberListItem from './TeamMemberListItem';
import EditingMember from './EditingMember'; // Import the newly created component
import { useTeamIdContext } from '../../hooks/contextHook';

const TeamMembers = () => {
const { teamId } = useTeamIdContext();
const [teamMembers, setTeamMembers] = useState([]);
const [editMemberId, setEditMemberId] = useState(null);
const [editMemberFirstName, setEditMemberFirstName] = useState('');
const [editMemberLastName, setEditMemberLastName] = useState('');
const [newMemberFirstName, setNewMemberFirstName] = useState('');
const [newMemberLastName, setNewMemberLastName] = useState('');
const [showAddModal, setShowAddModal] = useState(false);

useEffect(() => {
fetchTeamMembers();
}, []);

Check warning on line 21 in packages/client/src/components/Dashboard/TeamMembers.jsx

View workflow job for this annotation

GitHub Actions / build

React Hook useEffect has a missing dependency: 'fetchTeamMembers'. Either include it or remove the dependency array

const fetchTeamMembers = async () => {
try {
const response = await fetch(`${apiURL()}/teamMembers/${teamId}/members`);
if (!response.ok) {
throw new Error('Failed to fetch team members');
}
const data = await response.json();
setTeamMembers(data);
} catch (error) {
console.error(error);
}
};

const handleEditMember = (id) => {
setEditMemberId(id);
const memberToEdit = teamMembers.find((member) => member.member_id === id);
setEditMemberFirstName(memberToEdit.first_name);
setEditMemberLastName(memberToEdit.last_name);
};

const handleDeleteMember = async (id) => {
const confirmDelete = window.confirm(
'Are you sure you want to delete this team member?',
);
if (confirmDelete) {
try {
const response = await fetch(
`${apiURL()}/teamMembers/${teamId}/members/${id}`,
{
method: 'DELETE',
},
);
if (!response.ok) {
throw new Error('Failed to delete team member');
}
setTeamMembers((prevMembers) =>
prevMembers.filter((member) => member.member_id !== id),
);
} catch (error) {
console.error(error);
}
}
};

const handleAddMember = async () => {
if (!newMemberFirstName || !newMemberLastName) {
alert('Please enter both first name and last name.');
return;
}
try {
const response = await fetch(
`${apiURL()}/teamMembers/${teamId}/members`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
first_name: newMemberFirstName,
last_name: newMemberLastName,
}),
},
);
if (!response.ok) {
throw new Error('Failed to add team member');
}
fetchTeamMembers();
setShowAddModal(false);
setNewMemberFirstName('');
setNewMemberLastName('');
} catch (error) {
console.error(error);
}
};

const handleEditSave = async () => {
try {
const response = await fetch(
`${apiURL()}/teamMembers/${teamId}/members/${editMemberId}`,
{
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
first_name: editMemberFirstName,
last_name: editMemberLastName,
}),
},
);
if (!response.ok) {
throw new Error('Failed to edit team member');
}
fetchTeamMembers();
setEditMemberId(null);
setEditMemberFirstName('');
setEditMemberLastName('');
} catch (error) {
console.error(error);
}
};

return (
<div className="team-members-container">
<div className="team-members-heading-container">
<Typography className="teamMembers-heading" variant="h6">
Team Members
</Typography>
<Button
type="button"
variant="contained"
size="big"
onClick={() => setShowAddModal(true)}
>
Add Team Member
</Button>
</div>
<div className="add-member">
<AddTeamMemberModal
showAddModal={showAddModal}
setShowAddModal={setShowAddModal}
newMemberFirstName={newMemberFirstName}
setNewMemberFirstName={setNewMemberFirstName}
newMemberLastName={newMemberLastName}
setNewMemberLastName={setNewMemberLastName}
handleAddMember={handleAddMember}
/>
</div>

{editMemberId && (
<EditingMember
editMemberId={editMemberId}
editMemberFirstName={editMemberFirstName}
editMemberLastName={editMemberLastName}
setEditMemberFirstName={setEditMemberFirstName}
setEditMemberLastName={setEditMemberLastName}
handleEditSave={handleEditSave}
setEditMemberId={setEditMemberId}
/>
)}

<div>
<ul>
{teamMembers.map((member) => (
<TeamMemberListItem
key={member.member_id}
member={member}
handleEditMember={handleEditMember}
handleDeleteMember={handleDeleteMember}
/>
))}
</ul>
</div>
</div>
);
};

export { TeamMembers };
Loading

0 comments on commit 270dd9e

Please sign in to comment.