All checks were successful
Pull Request CI / Merge Check (pull_request) Successful in 3m27s
250 lines
7.8 KiB
Vue
250 lines
7.8 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref, watch } from 'vue'
|
|
|
|
import { adminAssignUnit, getUnits } from '@/api/units'
|
|
import { getAllRanks } from '@/api/rank'
|
|
import { Button } from '@/components/ui/button'
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from '@/components/ui/dialog'
|
|
import { Field, FieldError, FieldLabel } from '@/components/ui/field'
|
|
import { Input } from '@/components/ui/input'
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/components/ui/select'
|
|
import MemberCard from './MemberCard.vue'
|
|
import type { Member } from '@shared/types/member'
|
|
import type { Rank } from '@shared/types/rank'
|
|
import type { Unit } from '@shared/types/units'
|
|
|
|
const props = defineProps<{
|
|
open: boolean
|
|
member: Member | null
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
'update:open': [value: boolean]
|
|
transferred: [value: { memberId: number; unitId: number; rankId: number; reason: string }]
|
|
}>()
|
|
|
|
const units = ref<Unit[]>([])
|
|
const ranks = ref<Rank[]>([])
|
|
const loadingUnits = ref(false)
|
|
const loadingRanks = ref(false)
|
|
const submitting = ref(false)
|
|
const formError = ref('')
|
|
|
|
const selectedUnitId = ref('')
|
|
const selectedRankId = ref('')
|
|
const selectedReason = ref('transfer_request')
|
|
const customReason = ref('')
|
|
|
|
const reasonOptions = [
|
|
{ label: 'Transfer Request', value: 'transfer_request' },
|
|
{ label: 'Leadership Vote', value: 'leadership_vote' },
|
|
{ label: 'Appointment', value: 'appointment' },
|
|
{ label: 'Step Down', value: 'step_down' },
|
|
{ label: 'Custom', value: 'custom' },
|
|
]
|
|
|
|
const resolvedReason = computed(() => {
|
|
if (selectedReason.value === 'custom') {
|
|
return customReason.value.trim()
|
|
}
|
|
return selectedReason.value
|
|
})
|
|
|
|
const canSubmit = computed(() => {
|
|
return !!props.member && !!selectedUnitId.value && !!selectedRankId.value && !!resolvedReason.value
|
|
})
|
|
|
|
function resolveDefaultRankId(member: Member | null): string {
|
|
if (!member || !member.rank) {
|
|
return ''
|
|
}
|
|
|
|
const normalizedMemberRank = member.rank.trim().toLowerCase()
|
|
const matchedRank = ranks.value.find((rank) => {
|
|
return rank.name.trim().toLowerCase() === normalizedMemberRank
|
|
|| rank.short_name.trim().toLowerCase() === normalizedMemberRank
|
|
})
|
|
|
|
return matchedRank ? String(matchedRank.id) : ''
|
|
}
|
|
|
|
function resetForm() {
|
|
selectedUnitId.value = ''
|
|
selectedRankId.value = ''
|
|
selectedReason.value = 'transfer_request'
|
|
customReason.value = ''
|
|
formError.value = ''
|
|
}
|
|
|
|
async function loadUnits() {
|
|
loadingUnits.value = true
|
|
formError.value = ''
|
|
try {
|
|
units.value = await getUnits()
|
|
} catch {
|
|
formError.value = 'Failed to load units. Please try again.'
|
|
} finally {
|
|
loadingUnits.value = false
|
|
}
|
|
}
|
|
|
|
async function loadRanks() {
|
|
loadingRanks.value = true
|
|
formError.value = ''
|
|
try {
|
|
ranks.value = await getAllRanks()
|
|
selectedRankId.value = resolveDefaultRankId(props.member)
|
|
} catch {
|
|
formError.value = 'Failed to load ranks. Please try again.'
|
|
} finally {
|
|
loadingRanks.value = false
|
|
}
|
|
}
|
|
|
|
watch(
|
|
() => props.open,
|
|
(isOpen) => {
|
|
if (isOpen) {
|
|
resetForm()
|
|
loadUnits()
|
|
loadRanks()
|
|
}
|
|
},
|
|
)
|
|
|
|
async function onSubmit() {
|
|
if (!props.member) {
|
|
return
|
|
}
|
|
|
|
if (!selectedUnitId.value) {
|
|
formError.value = 'Please select a target unit.'
|
|
return
|
|
}
|
|
|
|
if (!selectedRankId.value) {
|
|
formError.value = 'Please select a target rank.'
|
|
return
|
|
}
|
|
|
|
if (!resolvedReason.value) {
|
|
formError.value = 'Please select a reason or enter a custom reason.'
|
|
return
|
|
}
|
|
|
|
submitting.value = true
|
|
formError.value = ''
|
|
try {
|
|
const unitId = Number(selectedUnitId.value)
|
|
const rankId = Number(selectedRankId.value)
|
|
await adminAssignUnit(props.member.member_id, unitId, rankId, resolvedReason.value)
|
|
|
|
emit('transferred', {
|
|
memberId: props.member.member_id,
|
|
unitId,
|
|
rankId,
|
|
reason: resolvedReason.value,
|
|
})
|
|
emit('update:open', false)
|
|
} catch {
|
|
formError.value = 'Failed to transfer member. Please try again.'
|
|
} finally {
|
|
submitting.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Dialog :open="open" @update:open="emit('update:open', $event)">
|
|
<DialogContent class="sm:max-w-[425px]">
|
|
<DialogHeader>
|
|
<DialogTitle>Transfer Member</DialogTitle>
|
|
<DialogDescription>
|
|
Select a new unit assignment for
|
|
<MemberCard v-if="member" :member-id="member.member_id" />
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<form id="transferForm" @submit.prevent="onSubmit" class="space-y-4 py-2">
|
|
<Field>
|
|
<FieldLabel>Target Unit</FieldLabel>
|
|
<Select v-model="selectedUnitId" :disabled="loadingUnits || submitting">
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select unit" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem v-for="unit in units" :key="unit.id" :value="String(unit.id)">
|
|
{{ unit.name }}
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</Field>
|
|
|
|
<Field>
|
|
<FieldLabel>Target Rank</FieldLabel>
|
|
<Select v-model="selectedRankId" :disabled="loadingRanks || submitting">
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select rank" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem v-for="rank in ranks" :key="rank.id" :value="String(rank.id)">
|
|
{{ rank.name }}
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</Field>
|
|
|
|
<Field>
|
|
<FieldLabel>Reason</FieldLabel>
|
|
<Select v-model="selectedReason" :disabled="submitting">
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select reason" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem
|
|
v-for="reason in reasonOptions"
|
|
:key="reason.value"
|
|
:value="reason.value"
|
|
>
|
|
{{ reason.label }}
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</Field>
|
|
|
|
<Field v-if="selectedReason === 'custom'">
|
|
<FieldLabel>Custom Reason</FieldLabel>
|
|
<Input
|
|
v-model="customReason"
|
|
:disabled="submitting"
|
|
placeholder="Enter custom transfer reason"
|
|
/>
|
|
</Field>
|
|
|
|
<FieldError v-if="formError" :errors="[formError]" />
|
|
</form>
|
|
|
|
<DialogFooter class="gap-2">
|
|
<Button variant="ghost" @click="emit('update:open', false)">
|
|
Cancel
|
|
</Button>
|
|
<Button type="submit" form="transferForm" :disabled="!canSubmit || loadingUnits || loadingRanks || submitting">
|
|
{{ submitting ? 'Transferring...' : 'Transfer Member' }}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</template> |