Skip to content

Commit

Permalink
Merge pull request #146 from Code-Hammers/CHE-167/Dev-Branch-Pull
Browse files Browse the repository at this point in the history
Pull dev branch changes into CHE-167 Story branch
  • Loading branch information
brok3turtl3 authored Jun 15, 2024
2 parents 51888b5 + c9e1dd0 commit 9f774cc
Show file tree
Hide file tree
Showing 24 changed files with 1,413 additions and 14 deletions.
10 changes: 7 additions & 3 deletions .github/workflows/build-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ jobs:
JWT_SECRET: ${{ secrets.JWT_SECRET }}
steps:
- uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '18.17.1'
- name: Build Docker Image
run: docker build -t codehammers/ch-dev-dep-v2:latest -f Dockerfile-dev .
run: docker build -t codehammers/ch-dev-dep-v3:latest -f Dockerfile-dev .
- name: Install Root Dependencies
run: docker run codehammers/ch-dev-dep-v2:latest npm install
run: docker run codehammers/ch-dev-dep-v3:latest npm install
- name: Install Client Dependencies
run: docker run codehammers/ch-dev-dep-v2:latest /bin/sh -c "cd client && npm install"
run: docker run codehammers/ch-dev-dep-v3:latest /bin/sh -c "cd client && npm install"
- run: LINT_COMMAND=lint docker-compose -f docker-compose-lint.yml up --abort-on-container-exit
- run: docker-compose -f docker-compose-test.yml up --abort-on-container-exit
env:
Expand Down
3 changes: 3 additions & 0 deletions Dockerfile-postgres
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM postgres:16.3

COPY ./scripts/sql_db_init.sql /docker-entrypoint-initdb.d/
9 changes: 7 additions & 2 deletions client/src/AuthenticatedApp.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect } from 'react';
import { Route, Routes } from 'react-router-dom';
import { Route, Routes, useNavigate } from 'react-router-dom';
import Header from './components/Header/Header';
import MainPage from './pages/MainPage/MainPage';
import Forums from './pages/Forums/Forums';
Expand All @@ -8,7 +8,9 @@ import Profile from './pages/Profile/Profile';
import EditProfilePage from './pages/EditProfilePage/EditProfilePage';
import Directory from './pages/DirectoryPage/DirectoryPage';
import NotFoundPage from './pages/NotFoundPage/NotFoundPage';
import { useNavigate } from 'react-router-dom';
import ApplicationsPage from './pages/ApplicationsPage/ApplicationsPage';
import CreateApplicationPage from './pages/CreateApplicationPage/CreateApplicationPage';
import UpdateApplicationPage from './pages/UpdateApplicationPage/UpdateApplicationPage';

const AuthenticatedApp = () => {
const navigate = useNavigate();
Expand Down Expand Up @@ -44,6 +46,9 @@ const AuthenticatedApp = () => {
<Route path="/editProfile" element={<EditProfilePage />} />
<Route path="/forums" element={<Forums />} />
<Route path="/directory" element={<Directory />} />
<Route path="/applications" element={<ApplicationsPage />} />
<Route path="/create-application" element={<CreateApplicationPage />} />
<Route path="/update-application/:id" element={<UpdateApplicationPage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</div>
Expand Down
4 changes: 4 additions & 0 deletions client/src/app/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import userReducer from '../features/user/userSlice';
import profilesReducer from '../features/profiles/profilesSlice';
import userProfileReducer from '../features/userProfile/userProfileSlice';
import alumniReducer from '../features/alumni/alumniSlice';
import applicationReducer from '../features/applications/applicationSlice';
import applicationsReducer from '../features/applications/applicationsSlice';

export const store = configureStore({
reducer: {
user: userReducer,
profiles: profilesReducer,
userProfile: userProfileReducer,
alumni: alumniReducer,
application: applicationReducer,
applications: applicationsReducer,
},
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { useAppSelector } from '../../app/hooks';

interface IStatusCount {
status: string;
count: number;
}

const ApplicationDashboard = (): JSX.Element => {
const [totalApplications, setTotalApplications] = useState(0);
const [applicationsByStatus, setApplicationsByStatus] = useState<IStatusCount[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const user = useAppSelector((state) => state.user.userData);

useEffect(() => {
async function fetchAggregatedData() {
setLoading(true);
try {
const response = await axios.get(`/api/applications/aggregated-user-stats/${user?._id}`);
const { totalApplications = 0, applicationsByStatus = [] } = response.data || {};
setTotalApplications(totalApplications);
setApplicationsByStatus(applicationsByStatus);
setLoading(false);
} catch (err) {
const error = err as Error;
console.error('Error fetching aggregated data:', error);
setError(error.message);
setLoading(false);
}
}

fetchAggregatedData();
}, [user?._id]);

if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;

return (
<div className="bg-gray-800 p-4 rounded-lg shadow-lg mb-4 w-full max-w-4xl">
<h2 className="font-extrabold text-2xl mb-2">Dashboard</h2>
<table className="min-w-full divide-y divide-gray-700">
<thead>
<tr>
<th className="px-6 py-3 text-center text-xs font-medium text-gray-400 uppercase tracking-wider">
Total Applications
</th>
{applicationsByStatus.map((status) => (
<th
key={status.status}
className="px-6 py-3 text-center text-xs font-medium text-gray-400 uppercase tracking-wider"
>
{status.status}
</th>
))}
</tr>
</thead>
<tbody className="divide-y divide-gray-700">
<tr>
<td className="px-6 py-4 whitespace-nowrap text-sm text-center text-white">
{totalApplications}
</td>
{applicationsByStatus.map((status) => (
<td
key={status.status}
className="px-6 py-4 whitespace-nowrap text-sm text-center text-white"
>
{status.count}
</td>
))}
</tr>
</tbody>
</table>
</div>
);
};

export default ApplicationDashboard;
1 change: 1 addition & 0 deletions client/src/components/Forums/ForumsList/ForumsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const ForumsList = ({ onForumSelect, selectedForumId }: ForumsListProps) => {
return (
<div>
<h2 className="text-xl font-bold mb-4">Forums</h2>

<ul>
<li
onClick={() => onForumSelect(null)}
Expand Down
8 changes: 8 additions & 0 deletions client/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ const Header = () => {
>
Alumni
</Link>
<Link
to="/app/applications"
className={`text-lg md:text-xl ${
currentPath === 'main' ? 'text-gray-300' : 'hover:text-gray-300'
} transition transform hover:scale-105`}
>
Applications
</Link>
<Link
to="/app/profiles"
className={`text-lg md:text-xl ${
Expand Down
89 changes: 89 additions & 0 deletions client/src/features/applications/applicationSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
import { IApplicationFormData, IApplication } from '../../../types/applications';

interface ApplicationState {
application: IApplication | null;
status: 'idle' | 'loading' | 'failed' | 'creating' | 'updating' | 'deleting';
error: string | null;
}

const initialState: ApplicationState = {
application: null,
status: 'idle',
error: null,
};

export const createApplication = createAsyncThunk(
'application/createApplication',
async (applicationData: IApplicationFormData, thunkAPI) => {
try {
const response = await axios.post('/api/applications', applicationData);
return response.data;
} catch (error) {
let errorMessage = 'An error occurred during application creation';
if (axios.isAxiosError(error)) {
errorMessage = error.response?.data || errorMessage;
}
return thunkAPI.rejectWithValue(errorMessage);
}
},
);

export const updateApplication = createAsyncThunk(
'applications/updateApplication',
async ({ id, ...formData }: Partial<IApplicationFormData> & { id: number }, thunkAPI) => {
try {
const response = await axios.put(`/api/applications/${id}`, formData);
return response.data;
} catch (error) {
let errorMessage = 'An error occurred during application update';
if (axios.isAxiosError(error)) {
errorMessage = error.response?.data || errorMessage;
}
return thunkAPI.rejectWithValue(errorMessage);
}
},
);

//TODO Build out delete thunks

const applicationSlice = createSlice({
name: 'application',
initialState,
reducers: {
resetApplicationState(state) {
state.application = null;
state.status = 'idle';
state.error = null;
},
},
extraReducers: (builder) => {
builder
.addCase(createApplication.pending, (state) => {
state.status = 'creating';
})
.addCase(createApplication.fulfilled, (state, action) => {
state.application = action.payload;
state.status = 'idle';
})
.addCase(createApplication.rejected, (state, action) => {
state.status = 'failed';
state.error = action.payload as string;
})
.addCase(updateApplication.pending, (state) => {
state.status = 'updating';
})
.addCase(updateApplication.fulfilled, (state, action) => {
state.application = action.payload;
state.status = 'idle';
})
.addCase(updateApplication.rejected, (state, action) => {
state.status = 'failed';
state.error = action.payload as string;
});
},
});

export const { resetApplicationState } = applicationSlice.actions;
export default applicationSlice.reducer;
53 changes: 53 additions & 0 deletions client/src/features/applications/applicationsSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
import { IApplication } from '../../../types/applications';

interface ApplicationsState {
applications: IApplication[];
status: 'idle' | 'loading' | 'failed';
error: string | null;
}

const initialState: ApplicationsState = {
applications: [],
status: 'idle',
error: null,
};

export const fetchApplications = createAsyncThunk(
'applications/fetchApplications',
async (_, thunkAPI) => {
try {
const response = await axios.get('/api/applications');
return response.data;
} catch (error) {
let errorMessage = 'An error occurred during fetching applications';
if (axios.isAxiosError(error)) {
errorMessage = error.response?.data || errorMessage;
}
return thunkAPI.rejectWithValue(errorMessage);
}
},
);

const applicationsSlice = createSlice({
name: 'applications',
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchApplications.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchApplications.fulfilled, (state, action) => {
state.applications = action.payload;
state.status = 'idle';
})
.addCase(fetchApplications.rejected, (state, action) => {
state.status = 'failed';
state.error = action.payload as string;
});
},
});

export default applicationsSlice.reducer;
Loading

0 comments on commit 9f774cc

Please sign in to comment.