6 Commits
0.4.1 ... 0.4.2

Author SHA1 Message Date
0a2c6785c6 Merge pull request 'LOA-Fixes' (#112) from LOA-Fixes into main
All checks were successful
Continuous Integration / Update Development (push) Successful in 2m25s
Continuous Deployment / Update Deployment (push) Successful in 2m22s
Reviewed-on: #112
2025-12-16 18:16:34 -06:00
dac4de236b added pagination support for member LOA history 2025-12-16 19:14:08 -05:00
5e1351d033 Fixed performance issue with member search on LOA form 2025-12-16 19:07:42 -05:00
5f6c17361b Fixed active LOA banner not appearing after client state rework 2025-12-16 19:02:32 -05:00
eca4a75a6e updated admin LOA wording 2025-12-16 19:00:38 -05:00
9196a86570 fixed error preventing LOA form from providing user feedback on submit when used from admin panel 2025-12-16 19:00:27 -05:00
6 changed files with 70 additions and 27 deletions

View File

@@ -56,9 +56,13 @@ router.get("/me", async (req: Request, res: Response) => {
//get my LOA history //get my LOA history
router.get("/history", async (req: Request, res: Response) => { router.get("/history", async (req: Request, res: Response) => {
const user = req.user.id;
try { try {
const result = await getUserLOA(user); const user = req.user.id;
const page = Number(req.query.page) || undefined;
const pageSize = Number(req.query.pageSize) || undefined;
const result = await getUserLOA(user, page, pageSize);
res.status(200).json(result) res.status(200).json(result)
} catch (error) { } catch (error) {
console.error(error); console.error(error);

View File

@@ -36,7 +36,10 @@ export async function getAllLOA(page = 1, pageSize = 10): Promise<PagedData<LOAR
return output; return output;
} }
export async function getUserLOA(userId: number): Promise<LOARequest[]> { export async function getUserLOA(userId: number, page = 1, pageSize = 10): Promise<PagedData<LOARequest>> {
const offset = (page - 1) * pageSize;
const result: LOARequest[] = await pool.query(` const result: LOARequest[] = await pool.query(`
SELECT loa.*, members.name, t.name AS type_name SELECT loa.*, members.name, t.name AS type_name
FROM leave_of_absences AS loa FROM leave_of_absences AS loa
@@ -53,8 +56,12 @@ export async function getUserLOA(userId: number): Promise<LOARequest[]> {
WHEN loa.closed IS NOT NULL THEN 4 WHEN loa.closed IS NOT NULL THEN 4
END, END,
loa.start_date DESC loa.start_date DESC
`, [userId]) LIMIT ? OFFSET ?;`, [userId, pageSize, offset])
return result;
let loaCount = Number((await pool.query(`SELECT COUNT(*) as count FROM leave_of_absences WHERE member_id = ?;`, [userId]))[0].count);
let pageCount = loaCount / pageSize;
let output: PagedData<LOARequest> = { data: result, pagination: { page: page, pageSize: pageSize, total: loaCount, totalPages: pageCount } }
return output;
} }
export async function getUserActiveLOA(userId: number): Promise<LOARequest[]> { export async function getUserActiveLOA(userId: number): Promise<LOARequest[]> {

View File

@@ -30,10 +30,10 @@ const environment = import.meta.env.VITE_ENVIRONMENT;
<p>This is a development build of the application. Some features will be unavailable or unstable.</p> <p>This is a development build of the application. Some features will be unavailable or unstable.</p>
</AlertDescription> </AlertDescription>
</Alert> </Alert>
<Alert v-if="userStore.user?.LOAData?.[0]" class="m-2 mx-auto w-5xl" variant="info"> <Alert v-if="userStore.user?.LOAs?.[0]" class="m-2 mx-auto w-5xl" variant="info">
<AlertDescription class="flex flex-row items-center text-nowrap gap-5 mx-auto"> <AlertDescription class="flex flex-row items-center text-nowrap gap-5 mx-auto">
<p>You are on LOA until <strong>{{ formatDate(userStore.user?.LOAData?.[0].end_date) }}</strong></p> <p>You are on LOA until <strong>{{ formatDate(userStore.user?.LOAs?.[0].extended_till || userStore.user?.LOAs?.[0].end_date) }}</strong></p>
<Button variant="secondary" @click="async () => { await cancelLOA(userStore.user?.LOAData?.[0].id); userStore.loadUser(); }">End <Button variant="secondary" @click="async () => { await cancelLOA(userStore.user.LOAs?.[0].id); userStore.loadUser(); }">End
LOA</Button> LOA</Button>
</AlertDescription> </AlertDescription>
</Alert> </Alert>

View File

@@ -31,9 +31,9 @@ export async function adminSubmitLOA(request: LOARequest): Promise<{ id?: number
}); });
if (res.ok) { if (res.ok) {
return res.json(); return
} else { } else {
return { error: "Failed to submit LOA" }; throw new Error("Failed to submit LOA");
} }
} }
@@ -85,8 +85,18 @@ export async function getAllLOAs(page?: number, pageSize?: number): Promise<Page
}); });
} }
export function getMyLOAs(): Promise<LOARequest[]> { export function getMyLOAs(page?: number, pageSize?: number): Promise<PagedData<LOARequest>> {
return fetch(`${addr}/loa/history`, { const params = new URLSearchParams();
if (page !== undefined) {
params.set("page", page.toString());
}
if (pageSize !== undefined) {
params.set("pageSize", pageSize.toString());
}
return fetch(`${addr}/loa/history?${params}`, {
method: "GET", method: "GET",
credentials: 'include', credentials: 'include',
headers: { headers: {

View File

@@ -134,6 +134,22 @@ const maxEndDate = computed(() => {
return null; return null;
} }
}) })
const memberFilter = ref('');
const filteredMembers = computed(() => {
const q = memberFilter?.value?.toLowerCase() ?? ""
const results: Member[] = []
for (const m of members.value ?? []) {
if (!q || (m.displayName || m.member_name).toLowerCase().includes(q)) {
results.push(m)
if (results.length >= 50) break
}
}
return results
})
</script> </script>
<template> <template>
@@ -158,12 +174,13 @@ const maxEndDate = computed(() => {
:display-value="(id) => { :display-value="(id) => {
const m = members.find(mem => mem.member_id === id) const m = members.find(mem => mem.member_id === id)
return m ? m.displayName || m.member_name : '' return m ? m.displayName || m.member_name : ''
}" /> }" @input="memberFilter = $event.target.value" />
</ComboboxAnchor> </ComboboxAnchor>
<ComboboxList class="*:w-64"> <ComboboxList class="*:w-64">
<ComboboxEmpty class="text-muted-foreground w-full">No results</ComboboxEmpty> <ComboboxEmpty class="text-muted-foreground w-full">No results</ComboboxEmpty>
<ComboboxGroup> <ComboboxGroup>
<template v-for="member in members" :key="member.member_id"> <div class="max-h-80 overflow-y-scroll scrollbar-themed min-w-3xs">
<template v-for="member in filteredMembers" :key="member.member_id">
<ComboboxItem :value="member.member_id" <ComboboxItem :value="member.member_id"
class="data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative cursor-pointer select-none px-2 py-1.5 w-full"> class="data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative cursor-pointer select-none px-2 py-1.5 w-full">
{{ member.displayName || member.member_name }} {{ member.displayName || member.member_name }}
@@ -173,6 +190,7 @@ const maxEndDate = computed(() => {
</ComboboxItemIndicator> </ComboboxItemIndicator>
</ComboboxItem> </ComboboxItem>
</template> </template>
</div>
</ComboboxGroup> </ComboboxGroup>
</ComboboxList> </ComboboxList>
</Combobox> </Combobox>
@@ -286,8 +304,10 @@ const maxEndDate = computed(() => {
</h2> </h2>
<p class="max-w-md text-muted-foreground"> <p class="max-w-md text-muted-foreground">
Your Leave of Absence request has been submitted successfully. {{ adminMode ? 'You have successfully submitted a Leave of Absence on behalf of another member.' :
It will take effect on your selected start date. `Your Leave
of Absence request has been submitted successfully.
It will take effect on your selected start date.` }}
</p> </p>

View File

@@ -59,7 +59,9 @@ async function loadLOAs() {
LOAList.value = result.data; LOAList.value = result.data;
pageData.value = result.pagination; pageData.value = result.pagination;
} else { } else {
LOAList.value = await getMyLOAs(); let result = await getMyLOAs(pageNum.value, pageSize.value);
LOAList.value = result.data;
pageData.value = result.pagination;
} }
} }