Merge pull request '100-recruitment-ui-fixes' (#101) from 100-recruitment-ui-fixes into main
All checks were successful
Continuous Integration / Update Development (push) Successful in 2m31s

Reviewed-on: #101
This commit was merged in pull request #101.
This commit is contained in:
2025-12-15 12:34:30 -06:00
6 changed files with 50 additions and 58 deletions

View File

@@ -155,21 +155,13 @@ router.post('/approve/:id', [requireLogin, requireRole("Recruiter")], async (req
try {
const app = await getApplicationByID(appID);
const result = await approveApplication(appID);
//guard against failures
if (result.affectedRows != 1) {
throw new Error("Something went wrong approving the application");
}
await approveApplication(appID, approved_by);
//update user profile
await setUserState(app.member_id, MemberState.Member);
await pool.query('CALL sp_accept_new_recruit_validation(?, ?, ?, ?)', [Number(process.env.CONFIG_ID), app.member_id, approved_by, approved_by])
// let nextRank = await getRankByName('Recruit')
// await insertMemberRank(app.member_id, nextRank.id);
// //assign user to "pending basic"
// await assignUserToStatus(app.member_id, 1);
res.sendStatus(200);
} catch (err) {
console.error('Approve failed:', err);
@@ -178,12 +170,13 @@ router.post('/approve/:id', [requireLogin, requireRole("Recruiter")], async (req
});
// POST /application/deny/:id
router.post('/deny/:id', [requireLogin, requireRole("Recruiter")], async (req, res) => {
const appID = req.params.id;
router.post('/deny/:id', [requireLogin, requireRole("Recruiter")], async (req: Request, res: Response) => {
const appID = Number(req.params.id);
const approver = Number(req.user.id);
try {
const app = await getApplicationByID(appID);
await denyApplication(appID);
await denyApplication(appID, approver);
await setUserState(app.member_id, MemberState.Denied);
res.sendStatus(200);
} catch (err) {

View File

@@ -31,7 +31,7 @@ export async function getApplicationByID(appID: number): Promise<ApplicationRow>
return app[0];
}
export async function getApplicationList(): Promise<ApplicationListRow[]> {
export async function getApplicationList(page: number = 1, pageSize: number = 25): Promise<ApplicationListRow[]> {
const sql = `SELECT
member.name AS member_name,
app.id,
@@ -40,9 +40,11 @@ export async function getApplicationList(): Promise<ApplicationListRow[]> {
app.app_status
FROM applications AS app
LEFT JOIN members AS member
ON member.id = app.member_id;`
ON member.id = app.member_id
ORDER BY app.submitted_at DESC
LIMIT ? OFFSET ?;`
const rows: ApplicationListRow[] = await pool.query(sql);
const rows: ApplicationListRow[] = await pool.query(sql, [pageSize, page]);
return rows;
}
@@ -59,30 +61,35 @@ export async function getAllMemberApplications(memberID: number): Promise<Applic
}
export async function approveApplication(id: number) {
export async function approveApplication(id: number, approver: number) {
const sql = `
UPDATE applications
SET approved_at = NOW()
SET approved_at = NOW(), approved_by = ?
WHERE id = ?
AND approved_at IS NULL
AND denied_at IS NULL
`;
const result = await pool.execute(sql, id);
return result;
const result = await pool.execute(sql, [approver, id]);
console.log(result);
if (result.affectedRows == 1) {
return
} else {
throw new Error(`"Something went wrong approving application with ID ${id}`);
}
}
export async function denyApplication(id: number) {
export async function denyApplication(id: number, approver: number) {
const sql = `
UPDATE applications
SET denied_at = NOW()
SET denied_at = NOW(), approved_by = ?
WHERE id = ?
AND approved_at IS NULL
AND denied_at IS NULL
`;
const result = await pool.execute(sql, id);
const result = await pool.execute(sql, [approver, id]);
console.log(result);
if (result.affectedRows == 1) {
return
} else {

View File

@@ -94,16 +94,18 @@ export async function approveApplication(id: Number) {
const res = await fetch(`${addr}/application/approve/${id}`, { method: 'POST', credentials: 'include' })
if (!res.ok) {
console.error("Something went wrong approving the application")
throw new Error("Something went wrong approving the application");
}
return;
}
export async function denyApplication(id: Number) {
const res = await fetch(`${addr}/application/deny/${id}`, { method: 'POST', credentials: 'include' })
if (!res.ok) {
console.error("Something went wrong denying the application")
throw new Error("Something went wrong denyting the application");
}
return;
}
export async function restartApplication() {

View File

@@ -174,7 +174,7 @@ watch(() => showCoC.value, async () => {
<FormLabel>Have you ever served in the military?</FormLabel>
<FormControl>
<div class="flex items-center gap-2">
<Checkbox :checked="value ?? false" @update:checked="handleChange" :disabled="readOnly" />
<Checkbox :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
<span>Yes (checked) / No (unchecked)</span>
</div>
</FormControl>

View File

@@ -105,12 +105,21 @@ async function postApp(appData) {
}
async function handleApprove(id) {
console.log("hi");
await approveApplication(id);
try {
await approveApplication(id);
loadData(await loadApplication(Number(route.params.id), true))
} catch (error) {
console.error(error);
}
}
async function handleDeny(id) {
await denyApplication(id);
try {
await denyApplication(id);
loadData(await loadApplication(Number(route.params.id), true))
} catch (error) {
console.error(error);
}
}
</script>
@@ -164,7 +173,8 @@ async function handleDeny(id) {
</ApplicationForm>
<div v-if="!newApp" class="pb-15">
<h3 class="scroll-m-20 text-2xl font-semibold tracking-tight mb-4">Discussion</h3>
<ApplicationChat :messages="chatData" @post="postComment" @post-internal="postCommentInternal" :admin-mode="finalMode === 'view-recruiter'">
<ApplicationChat :messages="chatData" @post="postComment" @post-internal="postCommentInternal"
:admin-mode="finalMode === 'view-recruiter'">
</ApplicationChat>
</div>
</div>

View File

@@ -11,7 +11,7 @@ import {
TableRow,
} from '@/components/ui/table'
import Button from '@/components/ui/button/Button.vue';
import { onMounted, ref, watch } from 'vue';
import { computed, onMounted, ref, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { CheckIcon, XIcon } from 'lucide-vue-next';
import Application from './Application.vue';
@@ -52,36 +52,26 @@ function formatExact(iso) {
return isNaN(d) ? '' : exactFmt.format(d)
}
async function handleApprove(id) {
await approveApplication(id);
appList.value = await getAllApplications();
}
async function handleDeny(id) {
await denyApplication(id);
appList.value = await getAllApplications();
}
const router = useRouter();
function openApplication(id) {
if (!id) return;
router.push(`/administration/applications/${id}`)
openPanel.value = true;
}
function closeApplication() {
router.push('/administration/applications')
openPanel.value = false;
}
const route = useRoute();
watch(() => route.params.id, (newId) => {
if (newId === undefined) {
openPanel.value = false;
}
})
const openPanel = ref(false);
// const openPanel = ref(false);
const openPanel = computed(() => route.params.id !== undefined)
onMounted(async () => {
appList.value = await getAllApplications();
@@ -102,7 +92,7 @@ onMounted(async () => {
<TableHeader>
<TableRow>
<TableHead>User</TableHead>
<TableHead>Date Submitted</TableHead>
<TableHead class="text-right">Date Submitted</TableHead>
<TableHead class="text-right">Status</TableHead>
</TableRow>
</TableHeader>
@@ -117,20 +107,10 @@ onMounted(async () => {
<TableCell class="font-medium">
<MemberCard :memberId="app.member_id"></MemberCard>
</TableCell>
<TableCell :title="formatExact(app.submitted_at)">
<TableCell class="text-right" :title="formatExact(app.submitted_at)">
{{ formatAgo(app.submitted_at) }}
</TableCell>
<TableCell v-if="app.app_status === ApplicationStatus.Pending"
class="inline-flex items-end gap-2">
<Button variant="success" @click.stop="handleApprove(app.id)">
<CheckIcon />
</Button>
<Button variant="destructive" @click.stop="handleDeny(app.id)">
<XIcon />
</Button>
</TableCell>
<TableCell class="text-right font-semibold" :class="[
app.app_status === ApplicationStatus.Pending && 'text-yellow-500',
app.app_status === ApplicationStatus.Accepted && 'text-green-500',