improved recruiter controls for applications

This commit is contained in:
2025-09-02 12:11:58 -04:00
parent e43117b64f
commit caa6ffd41a
5 changed files with 52 additions and 60 deletions

View File

@@ -25,7 +25,7 @@ app.post('/application', async (req, res) => {
if (!app) return res.status(400).json({ error: 'Missing App payload' }); if (!app) return res.status(400).json({ error: 'Missing App payload' });
// TODO: replace with current user ID // TODO: replace with current user ID
const memberId = 2; const memberId = 1;
const sql = `INSERT INTO applications (member_id, app_version, app_data) VALUES (?, ?, ?);`; const sql = `INSERT INTO applications (member_id, app_version, app_data) VALUES (?, ?, ?);`;
const appVersion = 1; const appVersion = 1;
@@ -43,38 +43,33 @@ app.post('/application', async (req, res) => {
} }
}); });
app.get('/application/all', async (req, res) => {
try {
const sql = `SELECT
member.name AS member_name,
app.id,
app.member_id,
app.submitted_at,
app.app_status
FROM applications AS app
LEFT JOIN members AS member
ON member.id = app.member_id;`
// app.get('/application/me', async (req, res) => { const rows = await pool.query(sql);
// try {
// // TODO: replace with current user ID
// const applicationId = 1;
// const rows = await pool.query( res.status(200).json(rows);
// `SELECT app.*, } catch {
// member.name AS member_name console.error(err);
// FROM applications AS app res.status(500);
// INNER JOIN members AS member ON member.id = app.member_id }
// WHERE app.member_id = ?;`, });
// [applicationId]
// );
// if (!Array.isArray(rows) || rows.length === 0) {
// return res.sendStatus(204);
// }
// return res.status(200).json(rows[0]);
// } catch (err) {
// console.error('Query failed:', err);
// return res.status(500).json({ error: 'Failed to load application' });
// }
// });
app.get('/application/:id', async (req, res) => { app.get('/application/:id', async (req, res) => {
let appID = req.params.id; let appID = req.params.id;
//TODO: Replace with real user Authorization and whatnot //TODO: Replace with real user Authorization and whatnot
if (appID === "me") if (appID === "me")
appID = 1; appID = 2;
try { try {
const conn = await pool.getConnection() const conn = await pool.getConnection()
@@ -84,7 +79,7 @@ app.get('/application/:id', async (req, res) => {
member.name AS member_name member.name AS member_name
FROM applications AS app FROM applications AS app
INNER JOIN members AS member ON member.id = app.member_id INNER JOIN members AS member ON member.id = app.member_id
WHERE app.id = 1;`, WHERE app.id = ?;`,
[appID] [appID]
); );
@@ -118,28 +113,6 @@ app.get('/application/:id', async (req, res) => {
} }
}) })
app.get('/application/all', async (req, res) => {
try {
const sql = `SELECT
member.name AS member_name,
app.id,
app.member_id,
app.submitted_at,
app.app_status
FROM applications AS app
LEFT JOIN members AS member
ON member.id = app.member_id;`
const rows = await pool.query(sql);
res.status(200).json(rows);
} catch {
console.error(err);
res.status(500);
}
});
app.post('/application/approve/:id', async (req, res) => { app.post('/application/approve/:id', async (req, res) => {
const appID = req.params.id; const appID = req.params.id;

View File

@@ -16,9 +16,10 @@ import ManageApplications from './pages/ManageApplications.vue';
<Button variant="link">Link</Button> <Button variant="link">Link</Button>
</div> </div>
<Separator></Separator> <Separator></Separator>
<Application></Application> <!-- <Application></Application> -->
<!-- <ManageApplications></ManageApplications> --> <!-- <ManageApplications></ManageApplications> -->
<!-- <AutoForm class="max-w-3xl mx-auto my-20"></AutoForm> --> <!-- <AutoForm class="max-w-3xl mx-auto my-20"></AutoForm> -->
<RouterView></RouterView>
</div> </div>
</template> </template>

View File

@@ -86,11 +86,12 @@ export async function postApplication(val: any) {
let out = { let out = {
"App": val, "App": val,
} }
await fetch(`http://${addr}/application`, { const res = await fetch(`http://${addr}/application`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(out), body: JSON.stringify(out),
}) })
return res;
} }
export async function postChatMessage(message: any, post_id: number) { export async function postChatMessage(message: any, post_id: number) {

View File

@@ -46,6 +46,15 @@ async function postComment(comment) {
chatData.value.push(await postChatMessage(comment, appID.value)); chatData.value.push(await postChatMessage(comment, appID.value));
} }
async function postApp(appData) {
const res = await postApplication(appData);
if (res.ok) {
readOnly.value = true;
newApp.value = false;
}
// TODO: Handle fail to post
}
</script> </script>
<template> <template>
@@ -82,12 +91,13 @@ async function postComment(comment) {
<div v-else class="flex flex-row justify-between items-center py-2 mb-8"> <div v-else class="flex flex-row justify-between items-center py-2 mb-8">
<h3 class="scroll-m-20 text-2xl font-semibold tracking-tight">Apply to join the 17th Rangers</h3> <h3 class="scroll-m-20 text-2xl font-semibold tracking-tight">Apply to join the 17th Rangers</h3>
</div> </div>
<ApplicationForm :read-only="readOnly" :data="appData" @submit="(e) => { postApplication(e) }" class="mb-7"> <ApplicationForm :read-only="readOnly" :data="appData" @submit="(e) => {postApp(e)}" class="mb-7">
</ApplicationForm> </ApplicationForm>
<div v-if="!newApp"> <div v-if="!newApp">
<h3 class="scroll-m-20 text-2xl font-semibold tracking-tight mb-4">Discussion</h3> <h3 class="scroll-m-20 text-2xl font-semibold tracking-tight mb-4">Discussion</h3>
<ApplicationChat :messages="chatData" @post="postComment"></ApplicationChat> <ApplicationChat :messages="chatData" @post="postComment"></ApplicationChat>
</div> </div>
</div> </div>
<div v-else>HELLOOO</div> <!-- TODO: Implement some kinda loading screen -->
<div v-else>Loading</div>
</template> </template>

View File

@@ -11,6 +11,7 @@ import {
} from '@/components/ui/table' } from '@/components/ui/table'
import Button from '@/components/ui/button/Button.vue'; import Button from '@/components/ui/button/Button.vue';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
const appList = ref([]); const appList = ref([]);
const now = Date.now(); const now = Date.now();
@@ -57,6 +58,9 @@ async function handleDeny(id) {
appList.value = await getAllApplications(); appList.value = await getAllApplications();
} }
function openApplication(id) {
useRouter().push('/hi')
}
onMounted(async () => { onMounted(async () => {
appList.value = await getAllApplications(); appList.value = await getAllApplications();
@@ -69,25 +73,28 @@ onMounted(async () => {
<TableRow> <TableRow>
<TableHead class="w-[100px]">User</TableHead> <TableHead class="w-[100px]">User</TableHead>
<TableHead>Date Submitted</TableHead> <TableHead>Date Submitted</TableHead>
<TableHead class="text-right"></TableHead>
<TableHead class="text-right">Status</TableHead> <TableHead class="text-right">Status</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
<TableRow v-for="app in appList" :key="app.id"> <TableRow v-for="app in appList" :key="app.id" class="cursor-pointer"
:onClick="() => { openApplication(app.id) }">
<TableCell class="font-medium">{{ app.member_name }}</TableCell> <TableCell class="font-medium">{{ app.member_name }}</TableCell>
<TableCell :title="formatExact(app.submitted_at)"> <TableCell :title="formatExact(app.submitted_at)">
{{ formatAgo(app.submitted_at) }} {{ formatAgo(app.submitted_at) }}
</TableCell> </TableCell>
<TableCell v-if="app.app_status != 'Pending'" class="text-right" :class="[ <TableCell v-if="app.app_status === Status.Pending" class="inline-flex items-end gap-2">
'font-semibold', <Button variant="success" @click.stop="() => { console.log('hello') }">Accept</Button>
<Button variant="destructive" @click.stop="() => { handleDeny(app.id) }">Deny</Button>
</TableCell>
<TableCell class="text-right font-semibold" :class="[
,
app.app_status === Status.Pending && 'text-yellow-500', app.app_status === Status.Pending && 'text-yellow-500',
app.app_status === Status.Accepted && 'text-success', app.app_status === Status.Accepted && 'text-green-500',
app.app_status === Status.Denied && 'text-destructive' app.app_status === Status.Denied && 'text-destructive'
]">{{ app.app_status }}</TableCell> ]">{{ app.app_status }}</TableCell>
<TableCell v-else class="inline-flex items-end gap-2">
<Button variant="success" :onClick="() => { handleApprove(app.id) }">Approve</Button>
<Button variant="destructive" :onClick="() => { handleDeny(app.id) }">Deny</Button>
</TableCell>
</TableRow> </TableRow>
</TableBody> </TableBody>
</Table> </Table>