implemented some recruiter view features

This commit is contained in:
2025-08-25 16:49:00 -04:00
parent 9a40cd3967
commit 3dadaf3b24
5 changed files with 106 additions and 18 deletions

View File

@@ -17,7 +17,7 @@ import ManageApplications from './pages/ManageApplications.vue';
</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> -->
</div> </div>
</template> </template>

View File

@@ -50,7 +50,7 @@ export async function postApplication(val: any) {
} }
export async function postChatMessage(val: any) { export async function postChatMessage(val: any) {
let output = { let output = {
message: val, message: val,
sender: 1, sender: 1,
@@ -62,4 +62,30 @@ export async function postChatMessage(val: any) {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(output), body: JSON.stringify(output),
}) })
}
export async function getAllApplications() {
const res = await fetch(`http://${addr}/application/all`)
if (res.ok) {
return await res.json();
} else {
console.error("Something went wrong approving the application")
}
}
export async function approveApplication(id: Number) {
const res = await fetch(`http://${addr}/application/approve/${id}`, { method: 'POST' })
if (!res.ok) {
console.error("Something went wrong approving the application")
}
}
export async function denyApplication(id: Number) {
const res = await fetch(`http://${addr}/application/deny/${id}`, { method: 'POST' })
if (!res.ok) {
console.error("Something went wrong approving the application")
}
} }

View File

@@ -20,6 +20,8 @@
--accent-foreground: oklch(0.9243 0.1151 95.7459); --accent-foreground: oklch(0.9243 0.1151 95.7459);
--destructive: oklch(0.6368 0.2078 25.3313); --destructive: oklch(0.6368 0.2078 25.3313);
--destructive-foreground: oklch(1.0000 0 0); --destructive-foreground: oklch(1.0000 0 0);
--success: oklch(66.104% 0.16937 144.153);
--success-foreground: oklch(1.0000 0 0);
--border: oklch(0.3715 0 0); --border: oklch(0.3715 0 0);
--input: oklch(0.3715 0 0); --input: oklch(0.3715 0 0);
--ring: oklch(0.7686 0.1647 70.0804); --ring: oklch(0.7686 0.1647 70.0804);
@@ -67,6 +69,8 @@
--accent-foreground: oklch(0.9243 0.1151 95.7459); --accent-foreground: oklch(0.9243 0.1151 95.7459);
--destructive: oklch(0.6368 0.2078 25.3313); --destructive: oklch(0.6368 0.2078 25.3313);
--destructive-foreground: oklch(1.0000 0 0); --destructive-foreground: oklch(1.0000 0 0);
--success: oklch(66.104% 0.16937 144.153);
--success-foreground: oklch(1.0000 0 0);
--border: oklch(0.3715 0 0); --border: oklch(0.3715 0 0);
--input: oklch(0.3715 0 0); --input: oklch(0.3715 0 0);
--ring: oklch(0.7686 0.1647 70.0804); --ring: oklch(0.7686 0.1647 70.0804);
@@ -115,6 +119,8 @@
--color-accent-foreground: var(--accent-foreground); --color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive); --color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground); --color-destructive-foreground: var(--destructive-foreground);
--color-success: var(--success);
--color-success-foreground: var(--success-foreground);
--color-border: var(--border); --color-border: var(--border);
--color-input: var(--input); --color-input: var(--input);
--color-ring: var(--ring); --color-ring: var(--ring);
@@ -155,6 +161,7 @@
* { * {
@apply border-border outline-ring/50; @apply border-border outline-ring/50;
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }

View File

@@ -17,6 +17,8 @@ export const buttonVariants = cva(
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost: ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
success:
"bg-success text-success-foreground shadow-xs hover:bg-success/90",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
}, },
size: { size: {

View File

@@ -1,4 +1,5 @@
<script setup> <script setup>
import { getAllApplications, approveApplication, denyApplication, Status } from '@/api/application';
import { import {
Table, Table,
TableBody, TableBody,
@@ -8,32 +9,84 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from '@/components/ui/table' } from '@/components/ui/table'
import Button from '@/components/ui/button/Button.vue';
import { onMounted, ref } from 'vue';
const appList = ref([]);
const now = Date.now();
// relative time formatter (uses user locale)
const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' })
// exact date/time for tooltip
const exactFmt = new Intl.DateTimeFormat(undefined, {
dateStyle: 'medium', timeStyle: 'short', timeZone: 'America/Toronto'
})
function formatAgo(iso) {
const d = new Date(iso)
if (isNaN(d)) return ''
let diff = (d.getTime() - now) / 1000 // seconds relative to page load
const divisions = [
{ amount: 60, name: 'second' },
{ amount: 60, name: 'minute' },
{ amount: 24, name: 'hour' },
{ amount: 7, name: 'day' },
{ amount: 4.34524, name: 'week' }, // avg weeks per month
{ amount: 12, name: 'month' },
{ amount: Infinity, name: 'year' },
]
for (const div of divisions) {
if (Math.abs(diff) < div.amount) {
return rtf.format(Math.round(diff), div.name)
}
diff /= div.amount
}
}
function formatExact(iso) {
const d = new Date(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();
}
onMounted(async () => {
appList.value = await getAllApplications();
})
</script> </script>
<template> <template>
<Table> <Table>
<TableCaption>A list of your recent invoices.</TableCaption> <!-- <TableCaption>A list of your recent invoices.</TableCaption> -->
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead class="w-[100px]"> <TableHead class="w-[100px]">User</TableHead>
Invoice <TableHead>Date Submitted</TableHead>
</TableHead> <TableHead class="text-right">Status</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead class="text-right">
Amount
</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
<TableRow> <TableRow v-for="app in appList" :key="app.id">
<TableCell class="font-medium"> <TableCell class="font-medium">{{ app.member_name }}</TableCell>
INV001 <TableCell :title="formatExact(app.submitted_at)">
{{ formatAgo(app.submitted_at) }}
</TableCell> </TableCell>
<TableCell>Paid</TableCell> <TableCell v-if="app.app_status != 'Pending'" class="text-right" :class="[
<TableCell>Credit Card</TableCell> 'font-semibold',
<TableCell class="text-right"> app.app_status === Status.Pending && 'text-yellow-500',
$250.00 app.app_status === Status.Approved && 'text-success',
app.app_status === Status.Denied && 'text-destructive'
]">{{ 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> </TableCell>
</TableRow> </TableRow>
</TableBody> </TableBody>