improved recruiter controls for applications
This commit is contained in:
69
api/index.js
69
api/index.js
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user