Merge branch 'main' into promotions
This commit is contained in:
@@ -20,6 +20,7 @@ const decisionDate = ref<Date | null>(null);
|
||||
const submitDate = ref<Date | null>(null);
|
||||
const loading = ref<boolean>(true);
|
||||
const member_name = ref<string>();
|
||||
const notFound = ref<boolean>(false);
|
||||
|
||||
const props = defineProps<{
|
||||
mode?: "create" | "view-self" | "view-recruiter" | "view-self-id"
|
||||
@@ -29,6 +30,11 @@ const finalMode = ref<"create" | "view-self" | "view-recruiter" | "view-self-id"
|
||||
|
||||
function loadData(raw: ApplicationFull) {
|
||||
|
||||
if (!raw) {
|
||||
notFound.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const data = raw.application;
|
||||
|
||||
appID.value = data.id;
|
||||
@@ -129,6 +135,10 @@ async function handleDeny(id) {
|
||||
<div v-if="unauthorized" class="flex justify-center w-full my-10">
|
||||
You do not have permission to view this application.
|
||||
</div>
|
||||
<div v-else-if="notFound" class="flex justify-center w-full my-10 text-muted-foreground">
|
||||
Looks like you dont have an application, reach out to the administration team if you believe this is an
|
||||
error.
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-if="!newApp" class="flex flex-row justify-between items-center py-2 mb-8">
|
||||
<!-- Application header -->
|
||||
@@ -181,8 +191,7 @@ async function handleDeny(id) {
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- TODO: Implement some kinda loading screen -->
|
||||
<div v-else class="flex items-center justify-center h-full">
|
||||
<Spinner class="size-8"/>
|
||||
<Spinner class="size-8" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import { onMounted, ref, computed, reactive, watch } from 'vue';
|
||||
import { addMemberToRole, createRole, deleteRole, getRoles, removeMemberFromRole, Role } from '@/api/roles';
|
||||
import { addMemberToRole, createRole, deleteRole, getRoles, removeMemberFromRole } from '@/api/roles';
|
||||
import Badge from '@/components/ui/badge/Badge.vue';
|
||||
import {
|
||||
Dialog,
|
||||
@@ -34,8 +34,11 @@ import { Plus, X } from 'lucide-vue-next';
|
||||
import Separator from '@/components/ui/separator/Separator.vue';
|
||||
import Input from '@/components/ui/input/Input.vue';
|
||||
import Label from '@/components/ui/label/Label.vue';
|
||||
import { getMembers } from '@/api/member';
|
||||
import { Member } from '@shared/types/member';
|
||||
import { getAllLightMembers, getMembers } from '@/api/member';
|
||||
import { Member, MemberLight } from '@shared/types/member';
|
||||
import { Role } from '@shared/types/roles';
|
||||
import RoleView from '@/components/roles/roleView.vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const roles = ref<Role[]>([])
|
||||
const activeRole = ref<Role | null>(null)
|
||||
@@ -43,16 +46,9 @@ const showDialog = ref(false);
|
||||
const showCreateGroupDialog = ref(false);
|
||||
const addingMember = ref(false);
|
||||
const memberToAdd = ref<Member | null>(null);
|
||||
const route = useRoute();
|
||||
|
||||
const allMembers = ref<Member[]>([])
|
||||
const availableMembers = computed(() => {
|
||||
if (!activeRole.value) return [];
|
||||
return allMembers.value.filter(
|
||||
member => !activeRole.value!.members.some(
|
||||
roleMember => roleMember.member_id === member.member_id
|
||||
)
|
||||
);
|
||||
})
|
||||
const allMembers = ref<MemberLight[]>([])
|
||||
|
||||
type RoleDraft = {
|
||||
name: string
|
||||
@@ -117,141 +113,40 @@ async function handleCreateGroup() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAddMember() {
|
||||
//guard
|
||||
if (memberToAdd.value == null)
|
||||
return;
|
||||
|
||||
await addMemberToRole(memberToAdd.value.member_id, activeRole.value.id);
|
||||
roles.value = await getRoles();
|
||||
}
|
||||
|
||||
async function handleRemoveMember(memberId: number) {
|
||||
removeMemberFromRole(memberId, activeRole.value.id);
|
||||
roles.value = await getRoles();
|
||||
}
|
||||
|
||||
async function handleDeleteRole() {
|
||||
await deleteRole(activeRole.value.id);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
roles.value = await getRoles();
|
||||
allMembers.value = await getMembers();
|
||||
allMembers.value = await getAllLightMembers();
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<Dialog v-model:open="showDialog"
|
||||
v-on:update:open="() => { showDialog = false; addingMember = false; memberToAdd = null; }">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle class="flex justify-between items-center">{{ activeRole.name }}
|
||||
<Badge class="mr-5">
|
||||
{{ activeRole.color }}
|
||||
</Badge>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{{ activeRole.description }}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div class="mt-5">
|
||||
<div class="flex justify-between items-center">
|
||||
<p>Members with this role</p>
|
||||
</div>
|
||||
<Separator class="my-2"></Separator>
|
||||
<ul class="flex flex-col gap-2">
|
||||
<li v-for="member in activeRole.members" class="flex justify-between items-center">
|
||||
<p>{{ member.member_name }}</p>
|
||||
<X class="text-muted-foreground" @click="handleRemoveMember(member.member_id)"></X>
|
||||
</li>
|
||||
<div v-if="!addingMember" @click="addingMember = true"
|
||||
class="flex gap-2 text-muted-foreground hover:text-primary hover:cursor-pointer">
|
||||
<Plus :size="20"></Plus>Add Member
|
||||
</div>
|
||||
<div v-else class="flex gap-2">
|
||||
<Combobox v-model="memberToAdd" by="value">
|
||||
<ComboboxAnchor>
|
||||
<div class="relative w-full max-w-sm items-center">
|
||||
<ComboboxInput class="pl-9"
|
||||
:display-value="(member: Member) => member?.member_name"
|
||||
placeholder="Search Members" />
|
||||
</div>
|
||||
</ComboboxAnchor>
|
||||
<ComboboxList>
|
||||
<ComboboxEmpty>
|
||||
No Members.
|
||||
</ComboboxEmpty>
|
||||
<ComboboxGroup>
|
||||
<ComboboxItem v-for="member in availableMembers" :key="member.member_id"
|
||||
:value="member">
|
||||
{{ member.member_name }}
|
||||
</ComboboxItem>
|
||||
</ComboboxGroup>
|
||||
</ComboboxList>
|
||||
</Combobox>
|
||||
<Button variant="secondary" @click="addingMember = false">Cancel</Button>
|
||||
<Button @click="handleAddMember">Save</Button>
|
||||
</div>
|
||||
</ul>
|
||||
<div class="max-w-7xl mx-auto w-full my-[0.2dvh] h-[0.8dvh]">
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="scroll-m-20 text-2xl font-semibold tracking-tight my-5">Role Management</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-[minmax(400px,280px)_1fr] gap-5">
|
||||
<div class="w-full pr-5 border-r">
|
||||
<div class="space-y-1">
|
||||
<p class="px-3 py-2 text-xs font-medium text-muted-foreground uppercase">
|
||||
Roles
|
||||
</p>
|
||||
|
||||
<button v-for="r in roles" :key="r.id" @click="$router.push(`/administration/roles/${r.id}`)"
|
||||
:class="[
|
||||
'flex w-full items-center gap-3 rounded-md px-3 py-2 transition-colors cursor-pointer',
|
||||
Number(route.params.id) == r.id
|
||||
? 'bg-muted font-medium'
|
||||
: 'hover:bg-muted/50'
|
||||
]">
|
||||
<span class="h-2 w-2 rounded-full shrink-0" :style="{ backgroundColor: r.color }" />
|
||||
<span class="truncate">{{ r.name }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<!-- <Button @click="handleDeleteRole">Delete Group</Button> -->
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Dialog v-model:open="showCreateGroupDialog" v-on:update:open="() => { showCreateGroupDialog = false; }">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle class="flex justify-between items-center">Create Group</DialogTitle>
|
||||
<DialogDescription>Create a new group of members</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form class="mt-5 space-y-5" @submit.prevent="handleCreateGroup">
|
||||
<div>
|
||||
<Label class="mb-2 block" for="group-name">Group Name</Label>
|
||||
<Input id="group-name" v-model="roleDraft.name" :aria-invalid="!!draftErrors.name" />
|
||||
<p v-if="draftErrors.name" class="text-destructive text-sm mt-1">{{ draftErrors.name }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label class="mb-2 block" for="group-desc">Description</Label>
|
||||
<Input id="group-desc" v-model="roleDraft.description" />
|
||||
</div>
|
||||
<div>
|
||||
<Label class="mb-2 block" for="group-color">Color</Label>
|
||||
<Input id="group-color" type="color" v-model="roleDraft.color" />
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" @click="showCreateGroupDialog = false"
|
||||
:disabled="creating">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" :disabled="creating">
|
||||
{{ creating ? "Saving..." : "Save" }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="flex items-center justify-between my-4">
|
||||
<p>Groups</p>
|
||||
<!-- <Button @click="showCreateGroupDialog = true">+ Add New Group</Button> -->
|
||||
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-5">
|
||||
<Card v-for="value in roles" :key="value.id" @click="activeRole = value; showDialog = true"
|
||||
class="cursor-pointer">
|
||||
<CardHeader>
|
||||
<CardTitle class="flex justify-between items-center">{{ value.name }}
|
||||
<Badge>
|
||||
{{ value.color }}
|
||||
</Badge>
|
||||
</CardTitle>
|
||||
<CardDescription>{{ value.description }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter>
|
||||
<p class="text-muted-foreground">{{ value.members.length }} members</p>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<div>
|
||||
<RoleView :all-members="allMembers"></RoleView>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
import { ArrowUpDown, Funnel, Plus, Search, X } from 'lucide-vue-next';
|
||||
import { ArrowUpDown, ChevronDown, ChevronUp, Funnel, Plus, Search, X } from 'lucide-vue-next';
|
||||
import Button from '@/components/ui/button/Button.vue';
|
||||
import TrainingReportForm from '@/components/trainingReport/trainingReportForm.vue';
|
||||
import Checkbox from '@/components/ui/checkbox/Checkbox.vue';
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
PaginationNext,
|
||||
PaginationPrevious,
|
||||
} from '@/components/ui/pagination'
|
||||
import Tooltip from '@/components/tooltip/Tooltip.vue';
|
||||
|
||||
enum sidePanelState { view, create, closed };
|
||||
|
||||
@@ -53,6 +54,7 @@ watch(() => route.params.id, async (newID) => {
|
||||
return;
|
||||
}
|
||||
TRLoaded.value = false;
|
||||
expanded.value = null;
|
||||
viewTrainingReport(Number(route.params.id));
|
||||
})
|
||||
|
||||
@@ -126,6 +128,8 @@ function setPage(pagenum: number) {
|
||||
pageNum.value = pagenum;
|
||||
loadTrainingReports();
|
||||
}
|
||||
|
||||
const expanded = ref<number>(null);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -197,8 +201,8 @@ function setPage(pagenum: number) {
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<div class="mt-5 flex justify-between" :class="sidePanel !== sidePanelState.closed ? 'flex-col items-center' : 'items-center justify-between'"
|
||||
>
|
||||
<div class="mt-5 flex justify-between"
|
||||
:class="sidePanel !== sidePanelState.closed ? 'flex-col items-center' : 'items-center justify-between'">
|
||||
<div></div>
|
||||
<Pagination v-slot="{ page }" :items-per-page="pageData?.pageSize || 10" :total="pageData?.total || 10"
|
||||
:default-page="2" :page="pageNum" @update:page="setPage">
|
||||
@@ -214,7 +218,7 @@ function setPage(pagenum: number) {
|
||||
<PaginationNext />
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
<div class="flex items-center gap-3 text-sm" :class="sidePanel !== sidePanelState.closed ? 'mt-3' : ''" >
|
||||
<div class="flex items-center gap-3 text-sm" :class="sidePanel !== sidePanelState.closed ? 'mt-3' : ''">
|
||||
<p class="text-muted-foreground text-nowrap">Per page:</p>
|
||||
|
||||
<button v-for="size in pageSizeOptions" :key="size" @click="setPageSize(size)"
|
||||
@@ -255,89 +259,180 @@ function setPage(pagenum: number) {
|
||||
<!-- Trainers -->
|
||||
<div>
|
||||
<label class="scroll-m-20 text-xl font-semibold tracking-tight">Trainers</label>
|
||||
<div class="grid grid-cols-4 py-2 text-sm font-medium text-muted-foreground border-b">
|
||||
<div
|
||||
class="grid grid-cols-[1fr_1fr_2fr_3rem] py-2 text-sm font-medium text-muted-foreground border-b">
|
||||
<span>Name</span>
|
||||
<span class="">Role</span>
|
||||
<span class="text-right col-span-2">Remarks</span>
|
||||
<span class="text-right">Remarks</span>
|
||||
<span></span>
|
||||
</div>
|
||||
<div v-for="person in focusedTrainingTrainers"
|
||||
class="grid grid-cols-4 py-2 items-center border-b last:border-none">
|
||||
<div>
|
||||
<MemberCard v-if="person.attendee_id" :member-id="person.attendee_id"
|
||||
class="justify-self-start"></MemberCard>
|
||||
<p v-else>{{ person.attendee_name }}</p>
|
||||
<div v-for="person in focusedTrainingTrainers" class=" items-center border-b last:border-none"
|
||||
:class="expanded === person.attendee_id && 'bg-muted/20'">
|
||||
<div class="grid grid-cols-[1fr_1fr_2fr_3rem] items-center py-2">
|
||||
<div>
|
||||
<MemberCard v-if="person.attendee_id" :member-id="person.attendee_id"
|
||||
class="justify-self-start"></MemberCard>
|
||||
<p v-else>{{ person.attendee_name }}</p>
|
||||
</div>
|
||||
<p class="">{{ person.role.name }}</p>
|
||||
<p class="text-right px-2 truncate"
|
||||
:class="person.remarks == '' ? 'text-muted-foreground' : ''">
|
||||
{{ person.remarks == "" ?
|
||||
'--'
|
||||
: person.remarks }}</p>
|
||||
<div class="w-min mx-auto">
|
||||
<Button v-if="expanded === person.attendee_id" @click.stop="expanded = null"
|
||||
variant="ghost" size="icon">
|
||||
<ChevronUp class="size-6" />
|
||||
</Button>
|
||||
<Button v-else @click.stop="expanded = person.attendee_id" variant="ghost"
|
||||
size="icon">
|
||||
<ChevronDown class="size-6" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-full" v-if="expanded === person.attendee_id">
|
||||
<div class="px-6 pb-5 pt-2 space-y-2">
|
||||
<p class="text-sm font-medium text-foreground">
|
||||
Remarks
|
||||
</p>
|
||||
<p v-if="person.remarks"
|
||||
class="text-sm text-muted-foreground leading-relaxed whitespace-pre-wrap max-h-72 overflow-y-auto">
|
||||
{{ person.remarks }}
|
||||
</p>
|
||||
<p v-else class="text-sm text-muted-foreground italic">
|
||||
None provided
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="">{{ person.role.name }}</p>
|
||||
<p class="col-span-2 text-right px-2"
|
||||
:class="person.remarks == '' ? 'text-muted-foreground' : ''">
|
||||
{{ person.remarks == "" ?
|
||||
'--'
|
||||
: person.remarks }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- trainees -->
|
||||
<div>
|
||||
<div class="flex flex-col">
|
||||
<label class="scroll-m-20 text-xl font-semibold tracking-tight">Trainees</label>
|
||||
<div class="grid grid-cols-5 py-2 text-sm font-medium text-muted-foreground border-b">
|
||||
<div
|
||||
class="grid grid-cols-[1fr_5rem_5rem_2fr_3rem] py-2 text-sm font-medium text-muted-foreground border-b px-2">
|
||||
<span>Name</span>
|
||||
<span class="">Bookwork</span>
|
||||
<span class="">Qual</span>
|
||||
<span class="text-right col-span-2">Remarks</span>
|
||||
<span class="text-center">Bookwork</span>
|
||||
<span class="text-center">Qual</span>
|
||||
<span class="text-right">Remarks</span>
|
||||
<span class="w-15"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="person in focusedTrainingTrainees"
|
||||
class="grid grid-cols-5 py-2 items-center border-b last:border-none">
|
||||
<div>
|
||||
<MemberCard v-if="person.attendee_id" :member-id="person.attendee_id"
|
||||
class="justify-self-start"></MemberCard>
|
||||
<p v-else>{{ person.attendee_name }}</p>
|
||||
<div v-for="person in focusedTrainingTrainees" class="border-b last:border-none"
|
||||
:class="expanded === person.attendee_id && 'bg-muted/20'">
|
||||
<div class="grid grid-cols-[1fr_5rem_5rem_2fr_3rem] py-2 items-center mx-2">
|
||||
<div>
|
||||
<MemberCard v-if="person.attendee_id" :member-id="person.attendee_id"
|
||||
class="justify-self-start"></MemberCard>
|
||||
<p v-else>{{ person.attendee_name }}</p>
|
||||
</div>
|
||||
<Tooltip :open="!focusedTrainingReport.course.hasBookwork" class="mx-auto"
|
||||
message="This course does not have bookwork">
|
||||
<Checkbox :disabled="!focusedTrainingReport.course.hasBookwork"
|
||||
:model-value="person.passed_bookwork" class="pointer-events-none">
|
||||
</Checkbox>
|
||||
</Tooltip>
|
||||
<Tooltip :open="!focusedTrainingReport.course.hasQual" class="mx-auto"
|
||||
message="This course does not have a qualification">
|
||||
<Checkbox :disabled="!focusedTrainingReport.course.hasQual"
|
||||
:model-value="person.passed_qual" class="pointer-events-none ml-1">
|
||||
</Checkbox>
|
||||
</Tooltip>
|
||||
<p class="text-right truncate"
|
||||
:class="person.remarks == '' ? 'text-muted-foreground' : ''">
|
||||
{{ person.remarks == "" ?
|
||||
'--'
|
||||
: person.remarks }}</p>
|
||||
<div class="w-min mx-auto">
|
||||
<Button v-if="expanded === person.attendee_id" @click.stop="expanded = null"
|
||||
variant="ghost" size="icon">
|
||||
<ChevronUp class="size-6" />
|
||||
</Button>
|
||||
<Button v-else @click.stop="expanded = person.attendee_id" variant="ghost"
|
||||
size="icon">
|
||||
<ChevronDown class="size-6" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-full" v-if="expanded === person.attendee_id">
|
||||
<div class="px-6 pb-5 pt-2 space-y-2">
|
||||
<p class="text-sm font-medium text-foreground">
|
||||
Remarks
|
||||
</p>
|
||||
<p v-if="person.remarks"
|
||||
class="text-sm text-muted-foreground leading-relaxed whitespace-pre-wrap max-h-72 overflow-y-auto">
|
||||
{{ person.remarks }}
|
||||
</p>
|
||||
<p v-else class="text-sm text-muted-foreground italic">
|
||||
None provided
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Checkbox :disabled="!focusedTrainingReport.course.hasQual"
|
||||
:model-value="person.passed_bookwork" class="pointer-events-none ml-5">
|
||||
</Checkbox>
|
||||
<Checkbox :disabled="!focusedTrainingReport.course.hasQual"
|
||||
:model-value="person.passed_qual" class="pointer-events-none ml-1">
|
||||
</Checkbox>
|
||||
<p class="col-span-2 text-right px-2"
|
||||
:class="person.remarks == '' ? 'text-muted-foreground' : ''">
|
||||
{{ person.remarks == "" ?
|
||||
'--'
|
||||
: person.remarks }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- No Shows -->
|
||||
<div v-if="focusedNoShows.length != 0">
|
||||
<div class="flex flex-col">
|
||||
<label class="scroll-m-20 text-xl font-semibold tracking-tight">No Shows</label>
|
||||
<div class="grid grid-cols-5 py-2 text-sm font-medium text-muted-foreground border-b">
|
||||
<label class="scroll-m-20 text-xl font-semibold tracking-tight">
|
||||
No Shows
|
||||
</label>
|
||||
|
||||
<div
|
||||
class="grid grid-cols-[1fr_5rem_5rem_2fr_3rem] py-2 text-sm font-medium text-muted-foreground border-b">
|
||||
<span>Name</span>
|
||||
<!-- <span class="">Role</span>
|
||||
<span class="">Role</span> -->
|
||||
<div></div>
|
||||
<div></div>
|
||||
<span class="text-right col-span-2">Remarks</span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span class="text-right">Remarks</span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="person in focusedNoShows"
|
||||
class="grid grid-cols-5 py-2 items-center border-b last:border-none">
|
||||
<div>
|
||||
<MemberCard v-if="person.attendee_id" :member-id="person.attendee_id"
|
||||
class="justify-self-start"></MemberCard>
|
||||
<p v-else>{{ person.attendee_name }}</p>
|
||||
|
||||
<div v-for="person in focusedNoShows" :key="person.attendee_id ?? person.attendee_name"
|
||||
class="border-b last:border-none transition-colors"
|
||||
:class="expanded === person.attendee_id && 'bg-muted/20'">
|
||||
<!-- Row -->
|
||||
<div class="grid grid-cols-[1fr_5rem_5rem_2fr_3rem] py-2 items-center">
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<MemberCard v-if="person.attendee_id" :member-id="person.attendee_id" />
|
||||
<p v-else>{{ person.attendee_name }}</p>
|
||||
</div>
|
||||
|
||||
<div></div>
|
||||
<div></div>
|
||||
|
||||
<p class="text-right px-2 truncate" :class="!person.remarks && 'text-muted-foreground'">
|
||||
{{ person.remarks || '--' }}
|
||||
</p>
|
||||
|
||||
<Button variant="ghost" size="icon" @click.stop="expanded === person.attendee_id
|
||||
? expanded = null
|
||||
: expanded = person.attendee_id">
|
||||
<ChevronDown v-if="expanded !== person.attendee_id" class="size-5" />
|
||||
<ChevronUp v-else class="size-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div v-if="expanded === person.attendee_id" class="col-span-full">
|
||||
<div class="px-6 py-4 space-y-2">
|
||||
<p class="text-sm font-medium text-foreground">
|
||||
Remarks
|
||||
</p>
|
||||
|
||||
<p v-if="person.remarks"
|
||||
class="text-sm text-muted-foreground leading-relaxed whitespace-pre-wrap max-h-72 overflow-y-auto">
|
||||
{{ person.remarks }}
|
||||
</p>
|
||||
|
||||
<p v-else class="text-sm text-muted-foreground italic">
|
||||
None provided
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <Checkbox :default-value="person.passed_bookwork ? true : false" class="pointer-events-none">
|
||||
</Checkbox>
|
||||
<Checkbox :default-value="person.passed_qual ? true : false" class="pointer-events-none">
|
||||
</Checkbox> -->
|
||||
<div></div>
|
||||
<div></div>
|
||||
<p class="col-span-2 text-right px-2"
|
||||
:class="person.remarks == '' ? 'text-muted-foreground' : ''">
|
||||
{{ person.remarks == "" ?
|
||||
'--'
|
||||
: person.remarks }}</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<label class="scroll-m-20 text-xl font-semibold tracking-tight">Remarks</label>
|
||||
@@ -368,33 +463,3 @@ function setPage(pagenum: number) {
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Firefox */
|
||||
.scrollbar-themed {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #555 #1f1f1f;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
/* Chrome, Edge, Safari */
|
||||
.scrollbar-themed::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
/* slightly wider to allow padding look */
|
||||
}
|
||||
|
||||
.scrollbar-themed::-webkit-scrollbar-track {
|
||||
background: #1f1f1f;
|
||||
margin-left: 6px;
|
||||
/* ❗ adds space between content + scrollbar */
|
||||
}
|
||||
|
||||
.scrollbar-themed::-webkit-scrollbar-thumb {
|
||||
background: #555;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.scrollbar-themed::-webkit-scrollbar-thumb:hover {
|
||||
background: #777;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user