added support for posting an LOA

This commit is contained in:
2025-09-15 21:02:32 -04:00
parent 303b72a160
commit c347949920
4 changed files with 135 additions and 8 deletions

View File

@@ -15,11 +15,13 @@ const port = 3000;
const applicationsRouter = require('./routes/applications');
const { memberRanks, ranks} = require('./routes/ranks');
const members = require('./routes/users');
const loaHandler = require('./routes/loa')
app.use('/application', applicationsRouter);
app.use('/ranks', ranks);
app.use('/userRoles', memberRanks);
app.use('/members', members);
app.use('/loa', loaHandler);
app.listen(port, () => {
console.log(`Example app listening on port ${port} `)

View File

@@ -6,7 +6,38 @@ const pool = require('../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);
}
})
module.exports = router;

45
ui/src/api/loa.ts Normal file
View File

@@ -0,0 +1,45 @@
export type LOARequest = {
member_id: number;
filed_date: string; // ISO 8601 string
start_date: string; // ISO 8601 string
end_date: string; // ISO 8601 string
reason?: string;
};
const addr = "localhost:3000";
export async function submitLOA(request: LOARequest): Promise<{ id?: number; error?: string }> {
const res = await fetch(`http://${addr}/loa`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(request),
});
if (res.ok) {
return res.json();
} else {
return { error: "Failed to submit LOA" };
}
}
export async function getMyLOA(): Promise<LOARequest | null> {
const res = await fetch(`http://${addr}/loa/me`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (res.ok) {
const out = res.json();
if (!out) {
return null;
}
return out;
} else {
return null;
}
}

View File

@@ -1,7 +1,6 @@
<script setup lang="ts">
import { Check, Search } from "lucide-vue-next"
import { Combobox, ComboboxAnchor, ComboboxEmpty, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxItemIndicator, ComboboxList } from "@/components/ui/combobox"
import { getRanks, Rank } from "@/api/rank"
import { onMounted, ref } from "vue";
import { Member, getMembers } from "@/api/member";
@@ -22,10 +21,9 @@ import { CalendarIcon } from "lucide-vue-next"
import Input from "@/components/ui/input/Input.vue";
import Textarea from "@/components/ui/textarea/Textarea.vue";
import Separator from "@/components/ui/separator/Separator.vue";
import { submitLOA } from "@/api/loa"; // <-- import the submit function
const members = ref<Member[]>([])
const currentMember = ref<Member | null>(null);
defineProps({
@@ -33,7 +31,6 @@ defineProps({
type: Boolean,
default: false
}
})
const df = new DateFormatter("en-US", {
@@ -45,10 +42,57 @@ const value = ref({
end: new CalendarDate(2022, 1, 20).add({ days: 20 }),
}) as Ref<DateRange>
const reason = ref(""); // <-- reason for LOA
const submitting = ref(false);
const submitError = ref<string | null>(null);
const submitSuccess = ref(false);
onMounted(async () => {
members.value = await getMembers();
});
// Submit handler
async function handleSubmit() {
submitError.value = null;
submitSuccess.value = false;
submitting.value = true;
// Use currentMember if adminMode, otherwise use your own member id (stubbed as 89 here)
const member_id = currentMember.value?.member_id ?? 89;
// Format dates as ISO strings
const filed_date = toMariaDBDatetime(new Date());
const start_date = toMariaDBDatetime(value.value.start?.toDate(getLocalTimeZone()));
const end_date = toMariaDBDatetime(value.value.end?.toDate(getLocalTimeZone()));
if (!member_id || !filed_date || !start_date || !end_date) {
submitError.value = "Missing required fields";
submitting.value = false;
return;
}
const req = {
member_id,
filed_date,
start_date,
end_date,
reason: reason.value,
};
const result = await submitLOA(req);
submitting.value = false;
if (result.id) {
submitSuccess.value = true;
reason.value = "";
} else {
submitError.value = result.error || "Failed to submit LOA";
}
}
function toMariaDBDatetime(date: Date): string {
return date.toISOString().slice(0, 19).replace('T', ' ');
}
</script>
<template>
@@ -62,7 +106,6 @@ onMounted(async () => {
Policy goes here.
</p>
</div>
<Switch />
</div>
<div class="flex-1 flex flex-col gap-5">
<div class="flex w-full gap-5 ">
@@ -113,10 +156,16 @@ onMounted(async () => {
</PopoverContent>
</Popover>
</div>
<Textarea placeholder="Reason for LOA" class="w-full resize-none" />
<Textarea
v-model="reason"
placeholder="Reason for LOA"
class="w-full resize-none"
/>
<div class="flex justify-end">
<Button :onClick="() => { }" class="w-min">Submit</Button>
<Button :onClick="handleSubmit" :disabled="submitting" class="w-min">Submit</Button>
</div>
<div v-if="submitError" class="text-red-500 text-sm mt-2">{{ submitError }}</div>
<div v-if="submitSuccess" class="text-green-500 text-sm mt-2">LOA submitted successfully!</div>
</div>
</div>
</template>