From 62defe5b6d701eb7fe34d87ee2927244aed5538a Mon Sep 17 00:00:00 2001 From: ajdj100 Date: Wed, 10 Dec 2025 21:30:41 -0500 Subject: [PATCH] LOA backend refactor --- api/src/routes/loa.js | 56 ---------------------- api/src/routes/loa.ts | 80 +++++++++++++++++++++++++++++++ api/src/services/loaService.ts | 29 +++++++++++ shared/types/loa.ts | 24 ++++++++++ ui/src/api/loa.ts | 46 ++++++++++++++---- ui/src/components/loa/loaForm.vue | 3 +- 6 files changed, 172 insertions(+), 66 deletions(-) delete mode 100644 api/src/routes/loa.js create mode 100644 api/src/routes/loa.ts create mode 100644 api/src/services/loaService.ts create mode 100644 shared/types/loa.ts diff --git a/api/src/routes/loa.js b/api/src/routes/loa.js deleted file mode 100644 index c14bf24..0000000 --- a/api/src/routes/loa.js +++ /dev/null @@ -1,56 +0,0 @@ -const express = require('express'); -const router = express.Router(); - -import pool from '../db'; - -//post a new LOA -router.post("/", async (req, res) => { - const { member_id, filed_date, start_date, end_date, reason } = req.body; - - if (!member_id || !filed_date || !start_date || !end_date) { - return res.status(400).json({ error: "Missing required fields" }); - } - - try { - const result = await pool.query( - `INSERT INTO leave_of_absences - (member_id, filed_date, start_date, end_date, reason) - VALUES (?, ?, ?, ?, ?)`, - [member_id, filed_date, start_date, end_date, reason] - ); - res.sendStatus(201); - } catch (error) { - console.error(error); - res.status(500).send('Something went wrong', error); - } -}); - -//get my current LOA -router.get("/me", async (req, res) => { - //TODO: implement current user getter - const user = 89; - - try { - const result = await pool.query("SELECT * FROM leave_of_absences WHERE member_id = ?", [user]) - res.status(200).json(result) - } catch (error) { - console.error(error); - res.status(500).send(error); - } -}) - -router.get('/all', async (req, res) => { - try { - const result = await pool.query( - `SELECT loa.*, members.name - FROM leave_of_absences AS loa - INNER JOIN members ON loa.member_id = members.id; - `); - res.status(200).json(result) - } catch (error) { - console.error(error); - res.status(500).send(error); - } -}) - -module.exports = router; diff --git a/api/src/routes/loa.ts b/api/src/routes/loa.ts new file mode 100644 index 0000000..054977e --- /dev/null +++ b/api/src/routes/loa.ts @@ -0,0 +1,80 @@ +const express = require('express'); +const router = express.Router(); + +import { Request, Response } from 'express'; +import pool from '../db'; +import { createNewLOA, getAllLOA, getLoaTypes, getUserLOA } from '../services/loaService'; +import { LOARequest } from '@app/shared/types/loa'; + +//member posts LOA +router.post("/", async (req: Request, res: Response) => { + let LOARequest = req.body as LOARequest; + LOARequest.member_id = req.user.id; + LOARequest.created_by = req.user.id; + + console.log(LOARequest); + + try { + // const result = await pool.query( + // `INSERT INTO leave_of_absences + // (member_id, filed_date, start_date, end_date, reason) + // VALUES (?, ?, ?, ?, ?)`, + // [member_id, filed_date, start_date, end_date, reason] + // ); + await createNewLOA(LOARequest); + res.sendStatus(201); + } catch (error) { + console.error(error); + res.status(500).send(error); + } +}); + +//admin posts LOA +router.post("/admin", async (req: Request, res: Response) => { + let LOARequest = req.body as LOARequest; + LOARequest.created_by = req.user.id; + + console.log(LOARequest); + + try { + await createNewLOA(LOARequest); + res.sendStatus(201); + } catch (error) { + console.error(error); + res.status(500).send(error); + } +}); + +//get my current LOA +router.get("/me", async (req: Request, res: Response) => { + const user = req.user.id; + try { + const result = await getUserLOA(user); + res.status(200).json(result) + } catch (error) { + console.error(error); + res.status(500).send(error); + } +}) + +router.get('/all', async (req, res) => { + try { + const result = await getAllLOA(); + res.status(200).json(result) + } catch (error) { + console.error(error); + res.status(500).send(error); + } +}) + +router.get('/types', async (req: Request, res: Response) => { + try { + let out = await getLoaTypes(); + res.status(200).json(out); + } catch (error) { + res.status(500).json(error); + console.error(error); + } +}) + +module.exports = router; diff --git a/api/src/services/loaService.ts b/api/src/services/loaService.ts new file mode 100644 index 0000000..ae65128 --- /dev/null +++ b/api/src/services/loaService.ts @@ -0,0 +1,29 @@ +import pool from "../db"; +import { LOARequest, LOAType } from '@app/shared/types/loa' + +export async function getLoaTypes(): Promise { + return await pool.query('SELECT * FROM leave_of_absences_types;'); +} + +export async function getAllLOA(): Promise { + let res: LOARequest[] = await pool.query( + `SELECT loa.*, members.name, t.name AS type_name + FROM leave_of_absences AS loa + LEFT JOIN members ON loa.member_id = members.id + LEFT JOIN leave_of_absences_types AS t ON loa.type_id = t.id; + `) as LOARequest[]; + return res; +} + +export async function getUserLOA(userId: number): Promise { + const result: LOARequest[] = await pool.query("SELECT * FROM leave_of_absences WHERE member_id = ?", [userId]) + return result; +} + +export async function createNewLOA(data: LOARequest) { + const sql = `INSERT INTO leave_of_absences + (member_id, filed_date, start_date, end_date, type_id, reason) + VALUES (?, ?, ?, ?, ?, ?)`; + await pool.query(sql, [data.member_id, data.filed_date, data.start_date, data.end_date, data.type_id, data.reason]) + return; +} \ No newline at end of file diff --git a/shared/types/loa.ts b/shared/types/loa.ts new file mode 100644 index 0000000..c87bf87 --- /dev/null +++ b/shared/types/loa.ts @@ -0,0 +1,24 @@ +export interface LOARequest { + id?: number; + member_id?: number; + filed_date?: string; // ISO 8601 string + start_date: string; // ISO 8601 string + end_date: string; // ISO 8601 string + extended_till?: string; + type_id?: number; + reason?: string; + expired?: boolean; + closed?: boolean; + closed_by?: number; + created_by?: number; + + name?: string; //member name + type_name?: string; +}; + +export interface LOAType { + id: number; + name: string; + max_length_days: number; + extendable: boolean; +} \ No newline at end of file diff --git a/ui/src/api/loa.ts b/ui/src/api/loa.ts index 6f9314b..93168cb 100644 --- a/ui/src/api/loa.ts +++ b/ui/src/api/loa.ts @@ -1,12 +1,4 @@ -export type LOARequest = { - id?: number; - name?: string; - member_id: number; - filed_date: string; // ISO 8601 string - start_date: string; // ISO 8601 string - end_date: string; // ISO 8601 string - reason?: string; -}; +import { LOARequest, LOAType } from '@shared/types/loa' // @ts-ignore const addr = import.meta.env.VITE_APIHOST; @@ -17,6 +9,7 @@ export async function submitLOA(request: LOARequest): Promise<{ id?: number; err "Content-Type": "application/json", }, body: JSON.stringify(request), + credentials: 'include', }); if (res.ok) { @@ -26,6 +19,24 @@ export async function submitLOA(request: LOARequest): Promise<{ id?: number; err } } +export async function adminSubmitLOA(request: LOARequest): Promise<{ id?: number; error?: string }> { + const res = await fetch(`${addr}/loa/admin`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(request), + credentials: 'include', + }); + + if (res.ok) { + return res.json(); + } else { + return { error: "Failed to submit LOA" }; + } +} + + export async function getMyLOA(): Promise { const res = await fetch(`${addr}/loa/me`, { method: "GET", @@ -60,3 +71,20 @@ export function getAllLOAs(): Promise { } }); } + +export async function getLoaTypes(): Promise { + const res = await fetch(`${addr}/loa/types`, { + method: "GET", + credentials: 'include', + }); + + if (res.ok) { + const out = res.json(); + if (!out) { + return null; + } + return out; + } else { + return null; + } +} \ No newline at end of file diff --git a/ui/src/components/loa/loaForm.vue b/ui/src/components/loa/loaForm.vue index e970f0e..f234fb7 100644 --- a/ui/src/components/loa/loaForm.vue +++ b/ui/src/components/loa/loaForm.vue @@ -18,7 +18,8 @@ import { RangeCalendar } from "@/components/ui/range-calendar" import { cn } from "@/lib/utils"; import { CalendarIcon } from "lucide-vue-next" import Textarea from "@/components/ui/textarea/Textarea.vue"; -import { LOARequest, submitLOA } from "@/api/loa"; // <-- import the submit function +import { submitLOA } from "@/api/loa"; // <-- import the submit function +import { LOARequest } from "@shared/types/loa"; const members = ref([]) const currentMember = ref(null);