258 lines
9.3 KiB
Vue
258 lines
9.3 KiB
Vue
<template>
|
|
<Dialog
|
|
:visible="visible"
|
|
@update:visible="val => $emit('update:visible', val)"
|
|
modal
|
|
header="참가 신청/수정"
|
|
:style="{ width: '400px' }"
|
|
:closable="true"
|
|
@hide="$emit('close')"
|
|
>
|
|
<div class="flex flex-wrap gap-3">
|
|
<!-- 회원 선택 -->
|
|
<div class="w-full min-w-[200px]">
|
|
<label>회원</label>
|
|
<Select
|
|
v-model="selectedMember"
|
|
:options="memberOptions"
|
|
optionLabel="name"
|
|
placeholder="회원 선택"
|
|
class="w-full"
|
|
filter
|
|
filterPlaceholder="이름으로 검색"
|
|
>
|
|
<!-- 선택된 값 표시 -->
|
|
<template #value="slotProps">
|
|
<div v-if="slotProps.value && slotProps.value.isNewGuest" class="flex align-items-center text-primary font-bold">
|
|
<i class="pi pi-user-plus mr-2" /> 신규 게스트 추가
|
|
</div>
|
|
<div v-else-if="slotProps.value" class="flex align-items-center">
|
|
<Tag :value="slotProps.value.group" :severity="slotProps.value.group === '참가자' ? 'success' : 'info'" class="mr-2" />
|
|
<span style="font-weight:bold;">{{ slotProps.value.name }}</span>
|
|
<span style="color:#888; font-size:0.93em; margin-left: 0.5em;">
|
|
{{ slotProps.value.memberType }} / {{ slotProps.value.gender }} / 에버리지: {{ slotProps.value.average ?? '-' }}
|
|
</span>
|
|
</div>
|
|
<span v-else>
|
|
{{ slotProps.placeholder }}
|
|
</span>
|
|
</template>
|
|
<!-- 옵션 목록 표시 -->
|
|
<template #option="slotProps">
|
|
<div v-if="slotProps.option.isNewGuest" class="flex align-items-center text-primary font-bold">
|
|
<i class="pi pi-user-plus mr-2" /> 신규 게스트 추가
|
|
</div>
|
|
<div v-else class="flex align-items-center">
|
|
<Tag :value="slotProps.option.group" :severity="slotProps.option.group === '참가자' ? 'success' : 'info'" class="mr-2" />
|
|
<span style="font-weight:bold;">{{ slotProps.option.name }}</span>
|
|
<span style="color:#888; font-size:0.93em; margin-left: 0.5em;">
|
|
{{ slotProps.option.memberType }} / {{ slotProps.option.gender }} / 에버리지: {{ slotProps.option.average ?? '-' }}
|
|
</span>
|
|
</div>
|
|
</template>
|
|
</Select>
|
|
<small v-if="submitted && !selectedMemberId" class="p-error">회원을 선택해주세요.</small>
|
|
|
|
<!-- 신규 게스트 추가 입력폼 -->
|
|
<div v-if="selectedMember && selectedMember.isNewGuest" class="w-full border p-3 rounded bg-gray-50 mt-2">
|
|
<div class="mb-2">
|
|
<label>이름</label>
|
|
<InputText v-model="newGuest.name" class="w-full" />
|
|
</div>
|
|
<div class="mb-2">
|
|
<label>연락처</label>
|
|
<InputText v-model="newGuest.phone" class="w-full" />
|
|
</div>
|
|
<div class="mb-2">
|
|
<label>성별</label>
|
|
<Select v-model="newGuest.gender" :options="genderOptions" optionLabel="label" optionValue="value" placeholder="성별 선택" class="w-full" />
|
|
</div>
|
|
<div class="mb-2">
|
|
<label>에버리지</label>
|
|
<InputText v-model="newGuest.average" class="w-full" />
|
|
</div>
|
|
<Button label="게스트 추가" class="mt-1" @click="addNewGuest" :disabled="!newGuest.name || !newGuest.phone || !newGuest.gender" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 참가 상태 -->
|
|
<div class="w-full min-w-[200px]">
|
|
<label>참가 상태</label>
|
|
<Select
|
|
v-model="status"
|
|
:options="statusOptions"
|
|
optionLabel="label"
|
|
optionValue="value"
|
|
placeholder="상태를 선택하세요"
|
|
checkmark
|
|
class="w-full"
|
|
/>
|
|
<small v-if="submitted && !status" class="p-error">참가 상태를 선택해주세요.</small>
|
|
</div>
|
|
<!-- 회비 납부 상태 -->
|
|
<div class="flex-1 min-w-[200px]">
|
|
<label>회비 납부 상태</label>
|
|
<Select
|
|
v-model="paymentStatus"
|
|
:options="paymentStatusOptions"
|
|
optionLabel="label"
|
|
optionValue="value"
|
|
placeholder="납부 상태를 선택하세요"
|
|
checkmark
|
|
class="w-full"
|
|
/>
|
|
<small v-if="submitted && !paymentStatus" class="p-error">납부 상태를 선택해주세요.</small>
|
|
</div>
|
|
<!-- 비고 -->
|
|
<div class="w-full">
|
|
<label>비고</label>
|
|
<InputText v-model="comment" class="w-full" />
|
|
</div>
|
|
<div v-if="error" class="w-full p-error p-mb-2">{{ error }}</div>
|
|
<div class="w-full flex justify-content-end gap-2 mt-3">
|
|
<Button type="submit" label="확인" @click="submit" />
|
|
<Button type="button" label="취소" severity="secondary" @click="$emit('close')" />
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, watch } from 'vue';
|
|
import Tag from 'primevue/tag';
|
|
import InputText from 'primevue/inputtext';
|
|
import Select from 'primevue/select';
|
|
import Button from 'primevue/button';
|
|
const props = defineProps({
|
|
publicHash: String,
|
|
participants: Array,
|
|
availableMembers: Array,
|
|
visible: Boolean, // Dialog의 v-model:visible과 연결
|
|
eventStatus: String
|
|
});
|
|
const emits = defineEmits(['close', 'updated', 'update:visible']);
|
|
|
|
const selectedMember = ref(null);
|
|
const memberOptions = computed(() => {
|
|
const baseOptions = [
|
|
...(props.participants || []).map(m => ({ ...m, group: '참가자', id: m.participantId, participantId: m.participantId, memberId: m.memberId })),
|
|
...(props.availableMembers || []).map(m => ({ ...m, group: '미신청', id: m.memberId, participantId: null, memberId: m.memberId }))
|
|
].sort((a, b) => {
|
|
const nameA = a.name || '';
|
|
const nameB = b.name || '';
|
|
return nameA.localeCompare(nameB, 'ko');
|
|
});
|
|
if (props.eventStatus === '준비') {
|
|
baseOptions.push({ name: '신규 게스트 추가', isNewGuest: true });
|
|
}
|
|
return baseOptions;
|
|
});
|
|
|
|
|
|
const selectedParticipantId = ref('');
|
|
const selectedMemberId = ref('');
|
|
watch(selectedMember, (val) => {
|
|
selectedParticipantId.value = val ? val.participantId : '';
|
|
selectedMemberId.value = val ? val.memberId : '';
|
|
|
|
// 참가자라면 기존 정보 자동 세팅
|
|
if (val) {
|
|
const found = (props.participants || []).find(m => m.memberId === val.memberId);
|
|
status.value = found?.status || '';
|
|
paymentStatus.value = found?.paymentStatus || '';
|
|
comment.value = found?.comment || '';
|
|
}
|
|
});
|
|
|
|
const comment = ref('');
|
|
const status = ref('');
|
|
const paymentStatus = ref('');
|
|
const error = ref('');
|
|
const submitted = ref(false);
|
|
|
|
const statusOptions = [
|
|
{ label: '참가예정', value: '참가예정' },
|
|
{ label: '참가확정', value: '참가확정' },
|
|
{ label: '취소', value: '취소' }
|
|
];
|
|
const paymentStatusOptions = [
|
|
{ label: '미납', value: '미납' },
|
|
{ label: '납부완료', value: '납부완료' }
|
|
];
|
|
|
|
|
|
import PublicEventService from '@/services/PublicEventService';
|
|
|
|
const newGuest = ref({ name: '', phone: '', gender: '' });
|
|
const genderOptions = [
|
|
{ label: '남', value: '남성' },
|
|
{ label: '여', value: '여성' }
|
|
];
|
|
|
|
async function addNewGuest() {
|
|
error.value = '';
|
|
if (!newGuest.value.name || !newGuest.value.phone || !newGuest.value.gender) {
|
|
error.value = '이름, 연락처, 성별을 모두 입력하세요.';
|
|
return;
|
|
}
|
|
try {
|
|
// 실제 API 경로/파라미터는 상황에 맞게 수정 필요
|
|
const res = await PublicEventService.createGuest(props.publicHash, {
|
|
name: newGuest.value.name,
|
|
phone: newGuest.value.phone,
|
|
gender: newGuest.value.gender,
|
|
average: newGuest.value.average
|
|
});
|
|
if (!res || !res.data || !res.data.memberId) throw new Error('게스트 등록 실패');
|
|
// 새 게스트를 멤버 옵션에 추가하고 자동 선택
|
|
const guest = {
|
|
...res.data,
|
|
group: '참가자',
|
|
id: res.data.participantId,
|
|
participantId: res.data.participantId,
|
|
memberId: res.data.memberId
|
|
};
|
|
// options에 직접 push (reactivity 보장 위해 availableMembers에도 추가)
|
|
props.availableMembers.push(guest);
|
|
// 참가자 목록에도 추가
|
|
props.participants.push(guest);
|
|
// 선택값 세팅
|
|
selectedMember.value = guest;
|
|
selectedParticipantId.value = guest.participantId;
|
|
selectedMemberId.value = guest.memberId;
|
|
// 입력폼 초기화
|
|
newGuest.value = { name: '', phone: '', gender: '', average: '' };
|
|
} catch (e) {
|
|
error.value = e.message || '게스트 등록 중 오류 발생';
|
|
}
|
|
}
|
|
|
|
|
|
async function submit() {
|
|
submitted.value = true;
|
|
error.value = '';
|
|
if (!selectedMemberId.value || !status.value || !paymentStatus.value) {
|
|
error.value = '모든 항목을 입력해주세요.';
|
|
return;
|
|
}
|
|
try {
|
|
const res = await PublicEventService.registerParticipant(props.publicHash, {
|
|
participantId: selectedParticipantId.value,
|
|
memberId: selectedMemberId.value,
|
|
comment: comment.value,
|
|
status: status.value,
|
|
paymentStatus: paymentStatus.value
|
|
});
|
|
if (!res.ok) throw new Error('참가 신청에 실패했습니다.');
|
|
emits('updated');
|
|
emits('close');
|
|
} catch (e) {
|
|
error.value = e.message;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
|
|
</style>
|