Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CHE-189] Create Inactivity Alert System #135

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
65 changes: 64 additions & 1 deletion client/src/pages/ApplicationsPage/ApplicationsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
import { useAppSelector } from '../../app/hooks';
Expand All @@ -14,6 +14,7 @@ const ApplicationsPage = (): JSX.Element => {
async function fetchApplications() {
try {
const response = await axios.get(`/api/applications?user_id=${user?._id}`);

setApplications(response.data);
} catch (error) {
console.error('Error fetching applications:', error);
Expand All @@ -23,6 +24,37 @@ const ApplicationsPage = (): JSX.Element => {
fetchApplications();
}, []);

const calculateIsInactive = (application: IApplication) => {
const { last_updated, notification_period, notifications_paused } = application;
if (notifications_paused) return false;

const lastUpdatedDate = new Date(last_updated);
const notificationPeriodMs = (notification_period * 24 * 60 * 60 * 1000) / 60 / 60 / 24;
return new Date().getTime() - lastUpdatedDate.getTime() > notificationPeriodMs;
};

const handleTogglePause = async (id: number, pause: boolean) => {
try {
await axios.put(`/api/applications/${id}/pause-notifications`, { pause });
setApplications((prevApps) =>
prevApps.map((app) => (app.id === id ? { ...app, notifications_paused: pause } : app)),
);
} catch (error) {
console.error('Error updating notification pause:', error);
}
};

const handlePeriodChange = async (id: number, period: number) => {
try {
await axios.put(`/api/applications/${id}/notification-period`, { period });
setApplications((prevApps) =>
prevApps.map((app) => (app.id === id ? { ...app, notification_period: period } : app)),
);
} catch (error) {
console.error('Error updating notification period:', error);
}
};

return (
<div className="bg-gray-900 flex flex-col items-center justify-center min-h-screen p-4 pt-40 text-white">
<ApplicationDashboard />
Expand All @@ -42,6 +74,37 @@ const ApplicationsPage = (): JSX.Element => {
</div>
<div className="text-gray-400 text-sm ">Status: {application.status}</div>
<div className="text-gray-400 text-sm ">Notes: {application.general_notes}</div>
{calculateIsInactive(application) && (
<div className="text-red-500 text-sm font-bold">
This application needs attention!
</div>
)}
<div className="flex items-center mt-2">
<label className="mr-2 text-gray-400">Notification Period:</label>
<select
className="bg-gray-700 text-white p-1 rounded"
value={application.notification_period}
onChange={(e) => handlePeriodChange(application.id, parseInt(e.target.value))}
>
<option value={30}>30 seconds</option>
<option value={86400}>1 day</option>
<option value={259200}>3 days</option>
<option value={604800}>1 week</option>
<option value={2592000}>1 month</option>
</select>
</div>
<div className="flex items-center mt-2">
<label className="mr-2 text-gray-400">Notifications:</label>
<input
type="checkbox"
checked={!application.notifications_paused}
onChange={(e) => handleTogglePause(application.id, !e.target.checked)}
className="bg-gray-700 text-white p-1 rounded"
/>
<span className="ml-2 text-gray-400">
{application.notifications_paused ? 'Paused' : 'Active'}
</span>
</div>
<button
className="bg-blue-500 focus:outline-none focus:shadow-outline font-bold hover:bg-blue-700 mt-2 px-4 py-2 rounded text-white"
onClick={() => navigate(`/app/update-application/${application.id}`)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ const UpdateApplicationPage = (): JSX.Element => {

async function fetchApplication() {
try {
console.log('HITTT!!!!!');
const response = await axios.get(`/api/applications/${id}`);
const applicationData = response.data;
applicationData.date_applied = new Date(applicationData.date_applied)
Expand Down
3 changes: 3 additions & 0 deletions client/types/applications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ export interface IApplication {
company: string;
status: string;
general_notes: string;
last_updated: string;
notification_period: number;
notifications_paused: boolean;
}

export interface IApplicationFormData {
Expand Down
11 changes: 7 additions & 4 deletions scripts/sql_db_init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ CREATE TABLE applications (
quick_apply BOOLEAN NOT NULL,
date_applied TIMESTAMPTZ DEFAULT NOW(),
general_notes TEXT,
last_updated TIMESTAMPTZ DEFAULT NOW(),
notification_period INT DEFAULT 3,
notifications_paused BOOLEAN DEFAULT FALSE,
FOREIGN KEY (job_id) REFERENCES jobs(id),
FOREIGN KEY (status_id) REFERENCES statuses(id)
);
Expand All @@ -79,7 +82,7 @@ INSERT INTO jobs (title, company, location, description, url) VALUES
('Project Manager', 'Calabogie Zoo', 'Calabogie, ON', 'Developing new website', 'https://www.torontozoo.com');


INSERT INTO applications (job_id, status_id, user_id, quick_apply, date_applied, general_notes) VALUES
(1, 1, '6644c602515c654def9b2ae7', true, NOW(), 'Quick applied for Software Engineer at Dogs R Us.'),
(2, 1, '6644c768515c654def9b2b09', true, NOW(), 'Full CS style application.'),
(3, 2, '6644c7f7515c654def9b2b18', true, NOW(), 'Phone screen scheduled!');
INSERT INTO applications (job_id, status_id, user_id, quick_apply, date_applied, general_notes, last_updated, notification_period, notifications_paused) VALUES
(1, 1, '6644c602515c654def9b2ae7', true, NOW(), 'Quick applied for Software Engineer at Dogs R Us.', NOW(), 3, false),
(2, 1, '6644c768515c654def9b2b09', true, NOW(), 'Full CS style application.', NOW(), 3, false),
(3, 2, '6644c7f7515c654def9b2b18', true, NOW(), 'Phone screen scheduled!', NOW(), 3, false);
70 changes: 61 additions & 9 deletions server/controllers/applicationsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ const getAllApplications = async (req: Request, res: Response) => {
jobs.company,
jobs.title,
statuses.name AS status,
applications.general_notes
applications.general_notes,
applications.last_updated,
applications.notification_period,
applications.notifications_paused
FROM
applications
INNER JOIN jobs ON applications.job_id = jobs.id
Expand Down Expand Up @@ -69,8 +72,8 @@ const createApplication = async (req: Request, res: Response) => {
const job_id = jobResult.rows[0].id;

const applicationQuery = `
INSERT INTO applications (job_id, status_id, user_id, quick_apply, date_applied, general_notes)
VALUES ($1, $2, $3, $4, $5, $6)
INSERT INTO applications (job_id, status_id, user_id, quick_apply, date_applied, general_notes, last_updated)
VALUES ($1, $2, $3, $4, $5, $6, NOW())
RETURNING id
`;
const applicationValues = [
Expand Down Expand Up @@ -133,25 +136,36 @@ const getApplicationById = async (req: CustomRequest<{ id: string }>, res: Respo

const updateApplication = async (req: CustomRequest<{ id: string }>, res: Response) => {
const { id } = req.params;
if (!req.user || req.user.id !== id)
return res.status(401).json({ message: 'You are not authorized to retrieve those records' });

if (!req.user) {
return res.status(401).json({ message: 'You are not authorized to update this record' });
}

try {
const { id } = req.params;
const { job_id, status_id, user_id, quick_apply, date_applied, general_notes } = req.body;
const query = `
UPDATE applications
SET job_id = $1, status_id = $2, user_id = $3, quick_apply = $4, date_applied = $5, general_notes = $6
WHERE id = $7
SET job_id = $1, status_id = $2, user_id = $3, quick_apply = $4, date_applied = $5, general_notes = $6, last_updated = NOW()
WHERE id = $7 AND user_id = $8
RETURNING id
`;
await pool.query(query, [
const { rows } = await pool.query(query, [
job_id,
status_id,
user_id,
quick_apply,
date_applied,
general_notes,
id,
req.user.id,
]);

if (rows.length === 0) {
return res
.status(401)
.json({ message: 'You are not authorized to update this record or application not found' });
}

res.status(200).json({ message: 'Application updated successfully' });
} catch (error) {
console.error('Error updating application:', error);
Expand Down Expand Up @@ -190,11 +204,49 @@ const getAggregatedUserStats = async (req: CustomRequest<{ userId: string }>, re
}
};

const updateNotificationPeriod = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const { period } = req.body;

const query = `
UPDATE applications
SET notification_period = $1
WHERE id = $2
`;
await pool.query(query, [period, id]);
res.status(200).json({ message: 'Notification period updated successfully' });
} catch (error) {
console.error('Error updating notification period:', error);
res.status(500).json({ message: 'Internal server error' });
}
};

const pauseNotifications = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const { pause } = req.body;

const query = `
UPDATE applications
SET notifications_paused = $1
WHERE id = $2
`;
await pool.query(query, [pause, id]);
res.status(200).json({ message: `Notifications ${pause ? 'paused' : 'resumed'} successfully` });
} catch (error) {
console.error('Error pausing/resuming notifications:', error);
res.status(500).json({ message: 'Internal server error' });
}
};

export {
getAllApplications,
getStatuses,
createApplication,
updateApplication,
getApplicationById,
getAggregatedUserStats,
updateNotificationPeriod,
pauseNotifications,
};
4 changes: 4 additions & 0 deletions server/routes/applicationsRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
updateApplication,
getApplicationById,
getAggregatedUserStats,
updateNotificationPeriod,
pauseNotifications,
} from '../controllers/applicationsController';
import { protect } from '../middleware/authMiddleware';

Expand All @@ -17,5 +19,7 @@ router.get('/', protect, getAllApplications);
router.get('/:id', protect, getApplicationById);
router.post('/', protect, createApplication);
router.put('/:id', protect, updateApplication);
router.put('/:id/notification-period', protect, updateNotificationPeriod);
router.put('/:id/pause-notifications', protect, pauseNotifications);

export default router;
Loading