Implemented admin assign unit UI
All checks were successful
Pull Request CI / Merge Check (pull_request) Successful in 3m55s

This commit is contained in:
2026-03-02 20:16:28 -05:00
parent 54dcb9d389
commit a988545dda
7 changed files with 278 additions and 5 deletions

View File

@@ -0,0 +1,194 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { adminAssignUnit, getUnits } from '@/api/units'
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 { 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; reason: string }]
}>()
const units = ref<Unit[]>([])
const loadingUnits = ref(false)
const submitting = ref(false)
const formError = ref('')
const selectedUnitId = ref('')
const selectedReason = ref('transfer_request')
const customReason = ref('')
const reasonOptions = [
{ label: 'Transfer Request', value: 'transfer_request' },
{ label: 'Leadership Vote', value: 'leadership_vote' },
{ 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 && !!resolvedReason.value
})
function resetForm() {
selectedUnitId.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
}
}
watch(
() => props.open,
(isOpen) => {
if (isOpen) {
resetForm()
loadUnits()
}
},
)
async function onSubmit() {
if (!props.member) {
return
}
if (!selectedUnitId.value) {
formError.value = 'Please select a target unit.'
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)
await adminAssignUnit(props.member.member_id, unitId, resolvedReason.value)
emit('transferred', {
memberId: props.member.member_id,
unitId,
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>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 || submitting">
{{ submitting ? 'Transferring...' : 'Transfer Member' }}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template>