Began implementation for getting promotion history

This commit is contained in:
2025-12-16 22:28:32 -05:00
parent f9e5dacda8
commit 278690e094
8 changed files with 462 additions and 275 deletions

View File

@@ -21,29 +21,32 @@ import Button from '../ui/button/Button.vue';
import FieldError from '../ui/field/FieldError.vue';
import { getAllLightMembers } from '@/api/member';
import { Rank } from '@shared/types/rank';
import { getAllRanks } from '@/api/rank';
import { getAllRanks, submitRankChange } from '@/api/rank';
import { error } from 'console';
import Input from '../ui/input/Input.vue';
import Field from '../ui/field/Field.vue';
const { handleSubmit, errors, values } = useForm({
const { handleSubmit, errors, values, resetForm } = useForm({
validationSchema: toTypedSchema(batchPromotionSchema),
validateOnMount: false,
})
const submitForm = handleSubmit(
(vals) => {
console.log("VALID SUBMIT", vals);
},
(errors) => {
console.log("INVALID SUBMIT", errors);
async (vals) => {
try {
let output = vals;
output.promotions.map(p => p.start_date = new Date(p.start_date).toISOString())
await submitRankChange(output);
formSubmitted.value = true;
} catch (error) {
submitError.value = error;
console.error(error);
}
}
);
function onSubmit(vals) {
console.log('hi')
console.log(vals);
}
const submitError = ref<string>(null);
const formSubmitted = ref(false);
const allmembers = ref<MemberLight[]>([]);
const allRanks = ref<Rank[]>([]);
@@ -105,157 +108,146 @@ onMounted(async () => {
</script>
<template>
<form id="trainingForm" @submit.prevent="submitForm" class="w-full min-w-0 flex flex-col gap-6">
<VeeFieldArray name="promotions" v-slot="{ fields, push, remove }">
<FieldSet class="w-full min-w-0">
<div class="flex flex-col gap-2">
<FieldLegend class="text-lg tracking-tight">
Attendees
</FieldLegend>
<!--
<FieldDescription>
Add members who attended this session.
</FieldDescription> -->
<div class="h-4">
<p v-if="errors.promotions && typeof errors.promotions === 'string'"
class="text-sm text-red-500">
{{ errors.promotions }}
</p>
</div>
<!-- TABLE SHELL -->
<div class="relative w-full min-w-0">
<FieldGroup class="min-w-max">
<!-- HEADER -->
<div class="grid grid-cols-[200px_200px_150px_500px_1fr_auto]
gap-3 px-1 -mb-4
text-sm font-medium text-muted-foreground">
<div>Member</div>
<div>Rank</div>
<div>Date</div>
<div>Reason</div>
<div></div>
<div></div>
<div class="w-xl">
<form v-if="!formSubmitted" id="trainingForm" @submit.prevent="submitForm"
class="w-full min-w-0 flex flex-col gap-6">
<VeeFieldArray name="promotions" v-slot="{ fields, push, remove }">
<FieldSet class="w-full min-w-0">
<div class="flex flex-col gap-2">
<div>
<FieldLegend class="scroll-m-20 text-2xl font-semibold tracking-tight">
Promotion Form
</FieldLegend>
<div class="h-6">
<p v-if="errors.promotions && typeof errors.promotions === 'string'"
class="text-sm text-red-500">
{{ errors.promotions }}
</p>
</div>
<!-- BODY -->
<div class="flex flex-col gap-2">
<div v-for="(row, index) in fields" :key="row.key" class="grid grid-cols-[200px_200px_150px_1fr_auto]
gap-3 items-start">
<!-- Member -->
<VeeField :name="`promotions[${index}].member_id`" v-slot="{ field, errors }">
<div class="flex flex-col min-w-0">
<Combobox :model-value="field.value" @update:model-value="field.onChange"
:ignore-filter="true">
<ComboboxAnchor>
<ComboboxInput class="w-full pl-3" placeholder="Search members…"
:display-value="id =>
memberById.get(id)?.displayName ||
memberById.get(id)?.username
" @input="memberSearch = $event.target.value" />
</ComboboxAnchor>
<ComboboxList>
<ComboboxEmpty>No results</ComboboxEmpty>
<ComboboxGroup>
<div
class="max-h-80 overflow-y-auto min-w-[12rem] scrollbar-themed">
<ComboboxItem v-for="member in filteredMembers"
:key="member.id" :value="member.id">
{{ member.displayName || member.username }}
<ComboboxItemIndicator>
<Check />
</ComboboxItemIndicator>
</ComboboxItem>
</div>
</ComboboxGroup>
</ComboboxList>
</Combobox>
<div class="h-5">
<FieldError v-if="errors.length" :errors="errors" />
</div>
</div>
</VeeField>
<!-- Rank -->
<VeeField :name="`promotions[${index}].rank_id`" v-slot="{ field, errors }">
<div class="flex flex-col min-w-0">
<Combobox :model-value="field.value" @update:model-value="field.onChange"
:ignore-filter="true">
<ComboboxAnchor>
<ComboboxInput class="w-full pl-3" placeholder="Select rank"
:display-value="id => rankById.get(id)?.name"
@input="rankSearch = $event.target.value" />
</ComboboxAnchor>
<ComboboxList>
<ComboboxEmpty>No results</ComboboxEmpty>
<ComboboxGroup>
<ComboboxItem v-for="rank in filteredRanks" :key="rank.id"
:value="rank.id">
{{ rank.name }}
<ComboboxItemIndicator>
<Check />
</ComboboxItemIndicator>
</ComboboxItem>
</ComboboxGroup>
</ComboboxList>
</Combobox>
<div class="h-5">
<FieldError v-if="errors.length" :errors="errors" />
</div>
</div>
</VeeField>
<!-- Date -->
<VeeField :name="`promotions[${index}].start_date`" v-slot="{ field, errors }">
<Field>
<div>
<Input type="date" v-bind="field" />
</div>
<!-- TABLE SHELL -->
<div class="">
<FieldGroup class="">
<!-- HEADER -->
<div class="grid grid-cols-[200px_200px_150px_1fr_auto]
gap-3 px-1 -mb-4
text-sm font-medium text-muted-foreground">
<div>Member</div>
<div>Rank</div>
<div>Date</div>
<div></div>
<div></div>
</div>
<!-- BODY -->
<div class="flex flex-col gap-2">
<div v-for="(row, index) in fields" :key="row.key" class="grid grid-cols-[200px_200px_150px_1fr_auto]
gap-3 items-start">
<!-- Member -->
<VeeField :name="`promotions[${index}].member_id`" v-slot="{ field, errors }">
<div class="flex flex-col min-w-0">
<Combobox :model-value="field.value" @update:model-value="field.onChange"
:ignore-filter="true">
<ComboboxAnchor>
<ComboboxInput class="w-full pl-3" placeholder="Search members…"
:display-value="id =>
memberById.get(id)?.displayName ||
memberById.get(id)?.username
" @input="memberSearch = $event.target.value" />
</ComboboxAnchor>
<ComboboxList>
<ComboboxEmpty>No results</ComboboxEmpty>
<ComboboxGroup>
<div
class="max-h-80 overflow-y-auto min-w-[12rem] scrollbar-themed">
<ComboboxItem v-for="member in filteredMembers"
:key="member.id" :value="member.id">
{{ member.displayName || member.username }}
<ComboboxItemIndicator>
<Check />
</ComboboxItemIndicator>
</ComboboxItem>
</div>
</ComboboxGroup>
</ComboboxList>
</Combobox>
<div class="h-5">
<FieldError v-if="errors.length" :errors="errors" />
</div>
</div>
</Field>
</VeeField>
<!-- Reason -->
<VeeField :name="`promotions[${index}].reason`" v-slot="{ field, errors }">
<Field>
<div>
<Input v-bind="field" />
</VeeField>
<!-- Rank -->
<VeeField :name="`promotions[${index}].rank_id`" v-slot="{ field, errors }">
<div class="flex flex-col min-w-0">
<Combobox :model-value="field.value" @update:model-value="field.onChange"
:ignore-filter="true">
<ComboboxAnchor>
<ComboboxInput class="w-full pl-3" placeholder="Select rank"
:display-value="id => rankById.get(id)?.name"
@input="rankSearch = $event.target.value" />
</ComboboxAnchor>
<ComboboxList>
<ComboboxEmpty>No results</ComboboxEmpty>
<ComboboxGroup>
<div
class="max-h-80 overflow-y-auto min-w-[12rem] scrollbar-themed">
<ComboboxItem v-for="rank in filteredRanks" :key="rank.id"
:value="rank.id">
{{ rank.name }}
<ComboboxItemIndicator>
<Check />
</ComboboxItemIndicator>
</ComboboxItem>
</div>
</ComboboxGroup>
</ComboboxList>
</Combobox>
<div class="h-5">
<FieldError v-if="errors.length" :errors="errors" />
</div>
</div>
</Field>
</VeeField>
<!-- Remove -->
<div class="flex justify-center">
<Button type="button" variant="ghost" size="icon" @click="remove(index)">
<X />
</Button>
</VeeField>
<!-- Date -->
<VeeField :name="`promotions[${index}].start_date`" v-slot="{ field, errors }">
<Field>
<div>
<Input type="date" v-bind="field" />
<div class="h-5">
<FieldError v-if="errors.length" :errors="errors" />
</div>
</div>
</Field>
</VeeField>
<!-- Remove -->
<div class="flex justify-end">
<Button type="button" variant="ghost" size="icon" @click="remove(index)">
<X />
</Button>
</div>
</div>
</div>
</div>
</FieldGroup>
</FieldGroup>
</div>
<Button type="button" @click="push({})" class="w-full" variant="outline">
<Plus /> Member
</Button>
</div>
<Button type="button" @click="push({})" class="" variant="outline">
<Plus /> Member
</Button>
</FieldSet>
</VeeFieldArray>
<div class="flex justify-end items-center gap-5">
<p v-if="submitError" class="text-destructive">{{ submitError }}</p>
<Button type="submit">Submit</Button>
</div>
</form>
<div v-else>
<div class="flex flex-col max-w-sm justify-center gap-4 py-24 mx-auto">
<div class="text-left">
<h2 class="text-2xl font-semibold mb-2">Successfully Submitted</h2>
<p class="text-muted-foreground">Your promotions have been recorded.</p>
</div>
</FieldSet>
</VeeFieldArray>
<div class="flex justify-end">
<Button type="submit">Submit</Button>
<Button @click="() => { formSubmitted = false; resetForm(); }" variant="secondary">
Submit Another
</Button>
</div>
</div>
</form>
</div>
</template>