telephone updates
Build And Push Image / docker (push) Successful in 2m52s
Details
Build And Push Image / docker (push) Successful in 2m52s
Details
This commit is contained in:
parent
13fa95a9a2
commit
024eca02ac
|
|
@ -10,7 +10,7 @@
|
|||
<v-card-title class="d-flex align-center pa-6 bg-primary">
|
||||
<v-icon class="mr-3 text-white">mdi-account-edit</v-icon>
|
||||
<h2 class="text-h5 text-white font-weight-bold flex-grow-1">
|
||||
Edit Member: {{ member?.FullName || `${member?.['First Name']} ${member?.['Last Name']}` }}
|
||||
Edit Member: {{ member?.FullName || `${member?.first_name} ${member?.last_name}` }}
|
||||
</h2>
|
||||
<v-btn
|
||||
icon
|
||||
|
|
@ -32,81 +32,81 @@
|
|||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="form['First Name']"
|
||||
v-model="form.first_name"
|
||||
label="First Name"
|
||||
variant="outlined"
|
||||
:rules="[rules.required]"
|
||||
required
|
||||
:error="hasFieldError('First Name')"
|
||||
:error-messages="getFieldError('First Name')"
|
||||
:error="hasFieldError('first_name')"
|
||||
:error-messages="getFieldError('first_name')"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="form['Last Name']"
|
||||
v-model="form.last_name"
|
||||
label="Last Name"
|
||||
variant="outlined"
|
||||
:rules="[rules.required]"
|
||||
required
|
||||
:error="hasFieldError('Last Name')"
|
||||
:error-messages="getFieldError('Last Name')"
|
||||
:error="hasFieldError('last_name')"
|
||||
:error-messages="getFieldError('last_name')"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="form.Email"
|
||||
v-model="form.email"
|
||||
label="Email Address"
|
||||
type="email"
|
||||
variant="outlined"
|
||||
:rules="[rules.required, rules.email]"
|
||||
required
|
||||
:error="hasFieldError('Email')"
|
||||
:error-messages="getFieldError('Email')"
|
||||
:error="hasFieldError('email')"
|
||||
:error-messages="getFieldError('email')"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<PhoneInputWrapper
|
||||
v-model="form.Phone"
|
||||
<EnhancedPhoneInput
|
||||
v-model="form.phone"
|
||||
label="Phone Number"
|
||||
placeholder="Enter phone number"
|
||||
:error="hasFieldError('Phone')"
|
||||
:error-message="getFieldError('Phone')"
|
||||
:error="hasFieldError('phone')"
|
||||
:error-message="getFieldError('phone')"
|
||||
@phone-data="handlePhoneData"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="form['Date of Birth']"
|
||||
v-model="form.date_of_birth"
|
||||
label="Date of Birth"
|
||||
type="date"
|
||||
variant="outlined"
|
||||
:error="hasFieldError('Date of Birth')"
|
||||
:error-messages="getFieldError('Date of Birth')"
|
||||
:error="hasFieldError('date_of_birth')"
|
||||
:error-messages="getFieldError('date_of_birth')"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<MultipleNationalityInput
|
||||
v-model="form.Nationality"
|
||||
v-model="form.nationality"
|
||||
label="Nationality"
|
||||
:error="hasFieldError('Nationality')"
|
||||
:error-message="getFieldError('Nationality')"
|
||||
:error="hasFieldError('nationality')"
|
||||
:error-message="getFieldError('nationality')"
|
||||
:max-nationalities="3"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-textarea
|
||||
v-model="form.Address"
|
||||
v-model="form.address"
|
||||
label="Address"
|
||||
variant="outlined"
|
||||
rows="2"
|
||||
:error="hasFieldError('Address')"
|
||||
:error-messages="getFieldError('Address')"
|
||||
:error="hasFieldError('address')"
|
||||
:error-messages="getFieldError('address')"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
|
|
@ -118,25 +118,25 @@
|
|||
|
||||
<v-col cols="12" md="4">
|
||||
<v-select
|
||||
v-model="form['Membership Status']"
|
||||
v-model="form.membership_status"
|
||||
:items="membershipStatusOptions"
|
||||
label="Membership Status"
|
||||
variant="outlined"
|
||||
:rules="[rules.required]"
|
||||
required
|
||||
:error="hasFieldError('Membership Status')"
|
||||
:error-messages="getFieldError('Membership Status')"
|
||||
:error="hasFieldError('membership_status')"
|
||||
:error-messages="getFieldError('membership_status')"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="4">
|
||||
<v-text-field
|
||||
v-model="form['Member Since']"
|
||||
v-model="form.member_since"
|
||||
label="Member Since"
|
||||
type="date"
|
||||
variant="outlined"
|
||||
:error="hasFieldError('Member Since')"
|
||||
:error-messages="getFieldError('Member Since')"
|
||||
:error="hasFieldError('member_since')"
|
||||
:error-messages="getFieldError('member_since')"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
|
|
@ -146,30 +146,30 @@
|
|||
label="Current Year Dues Paid"
|
||||
color="success"
|
||||
inset
|
||||
:error="hasFieldError('Current Year Dues Paid')"
|
||||
:error-messages="getFieldError('Current Year Dues Paid')"
|
||||
:error="hasFieldError('current_year_dues_paid')"
|
||||
:error-messages="getFieldError('current_year_dues_paid')"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6" v-if="duesPaid">
|
||||
<v-text-field
|
||||
v-model="form['Membership Date Paid']"
|
||||
v-model="form.membership_date_paid"
|
||||
label="Payment Date"
|
||||
type="date"
|
||||
variant="outlined"
|
||||
:error="hasFieldError('Membership Date Paid')"
|
||||
:error-messages="getFieldError('Membership Date Paid')"
|
||||
:error="hasFieldError('membership_date_paid')"
|
||||
:error-messages="getFieldError('membership_date_paid')"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6" v-if="!duesPaid">
|
||||
<v-text-field
|
||||
v-model="form['Payment Due Date']"
|
||||
v-model="form.payment_due_date"
|
||||
label="Payment Due Date"
|
||||
type="date"
|
||||
variant="outlined"
|
||||
:error="hasFieldError('Payment Due Date')"
|
||||
:error-messages="getFieldError('Payment Due Date')"
|
||||
:error="hasFieldError('payment_due_date')"
|
||||
:error-messages="getFieldError('payment_due_date')"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
|
@ -201,7 +201,6 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import type { Member } from '~/utils/types';
|
||||
import { formatBooleanAsString } from '~/server/utils/nocodb';
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean;
|
||||
|
|
@ -221,20 +220,20 @@ const formRef = ref();
|
|||
const formValid = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// Form data
|
||||
// Form data - using snake_case field names
|
||||
const form = ref({
|
||||
'First Name': '',
|
||||
'Last Name': '',
|
||||
Email: '',
|
||||
Phone: '',
|
||||
'Date of Birth': '',
|
||||
Nationality: '',
|
||||
Address: '',
|
||||
'Membership Status': 'Active',
|
||||
'Member Since': '',
|
||||
'Current Year Dues Paid': 'false',
|
||||
'Membership Date Paid': '',
|
||||
'Payment Due Date': ''
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
date_of_birth: '',
|
||||
nationality: '',
|
||||
address: '',
|
||||
membership_status: 'Active',
|
||||
member_since: '',
|
||||
current_year_dues_paid: 'false',
|
||||
membership_date_paid: '',
|
||||
payment_due_date: ''
|
||||
});
|
||||
|
||||
// Additional form state
|
||||
|
|
@ -246,20 +245,20 @@ const fieldErrors = ref<Record<string, string>>({});
|
|||
|
||||
// Watch dues paid switch
|
||||
watch(duesPaid, (newValue) => {
|
||||
form.value['Current Year Dues Paid'] = formatBooleanAsString(newValue);
|
||||
form.value.current_year_dues_paid = newValue ? 'true' : 'false';
|
||||
if (newValue) {
|
||||
form.value['Payment Due Date'] = '';
|
||||
if (!form.value['Membership Date Paid']) {
|
||||
form.value['Membership Date Paid'] = new Date().toISOString().split('T')[0];
|
||||
form.value.payment_due_date = '';
|
||||
if (!form.value.membership_date_paid) {
|
||||
form.value.membership_date_paid = new Date().toISOString().split('T')[0];
|
||||
}
|
||||
} else {
|
||||
form.value['Membership Date Paid'] = '';
|
||||
if (!form.value['Payment Due Date']) {
|
||||
form.value.membership_date_paid = '';
|
||||
if (!form.value.payment_due_date) {
|
||||
// Set due date to one year from member since date or today
|
||||
const memberSince = form.value['Member Since'] || new Date().toISOString().split('T')[0];
|
||||
const memberSince = form.value.member_since || new Date().toISOString().split('T')[0];
|
||||
const dueDate = new Date(memberSince);
|
||||
dueDate.setFullYear(dueDate.getFullYear() + 1);
|
||||
form.value['Payment Due Date'] = dueDate.toISOString().split('T')[0];
|
||||
form.value.payment_due_date = dueDate.toISOString().split('T')[0];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -304,28 +303,44 @@ const handlePhoneData = (data: any) => {
|
|||
phoneData.value = data;
|
||||
};
|
||||
|
||||
// Form pre-population
|
||||
// Form pre-population - Updated to use snake_case field names
|
||||
const populateForm = () => {
|
||||
if (!props.member) return;
|
||||
|
||||
console.log('[EditMemberDialog] Populating form with member data:', props.member);
|
||||
|
||||
const member = props.member;
|
||||
|
||||
// Convert date fields to proper format for input[type="date"]
|
||||
const formatDateForInput = (dateString: string) => {
|
||||
if (!dateString) return '';
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
return date.toISOString().split('T')[0];
|
||||
} catch {
|
||||
return dateString;
|
||||
}
|
||||
};
|
||||
|
||||
form.value = {
|
||||
'First Name': member['First Name'] || '',
|
||||
'Last Name': member['Last Name'] || '',
|
||||
Email: member.Email || '',
|
||||
Phone: member.Phone || '',
|
||||
'Date of Birth': member['Date of Birth'] || '',
|
||||
Nationality: member.Nationality || '',
|
||||
Address: member.Address || '',
|
||||
'Membership Status': member['Membership Status'] || 'Active',
|
||||
'Member Since': member['Member Since'] || '',
|
||||
'Current Year Dues Paid': member['Current Year Dues Paid'] || 'false',
|
||||
'Membership Date Paid': member['Membership Date Paid'] || '',
|
||||
'Payment Due Date': member['Payment Due Date'] || ''
|
||||
first_name: member.first_name || '',
|
||||
last_name: member.last_name || '',
|
||||
email: member.email || '',
|
||||
phone: member.phone || '',
|
||||
date_of_birth: formatDateForInput(member.date_of_birth || ''),
|
||||
nationality: member.nationality || '',
|
||||
address: member.address || '',
|
||||
membership_status: member.membership_status || 'Active',
|
||||
member_since: formatDateForInput(member.member_since || ''),
|
||||
current_year_dues_paid: member.current_year_dues_paid || 'false',
|
||||
membership_date_paid: formatDateForInput(member.membership_date_paid || ''),
|
||||
payment_due_date: formatDateForInput(member.payment_due_date || '')
|
||||
};
|
||||
|
||||
// Set dues paid switch based on the string value
|
||||
duesPaid.value = member['Current Year Dues Paid'] === 'true';
|
||||
duesPaid.value = member.current_year_dues_paid === 'true';
|
||||
|
||||
console.log('[EditMemberDialog] Form populated:', form.value);
|
||||
};
|
||||
|
||||
// Form submission
|
||||
|
|
@ -345,13 +360,13 @@ const handleSubmit = async () => {
|
|||
const memberData = { ...form.value };
|
||||
|
||||
// Ensure required fields are not empty
|
||||
if (!memberData['First Name']?.trim()) {
|
||||
if (!memberData.first_name?.trim()) {
|
||||
throw new Error('First Name is required');
|
||||
}
|
||||
if (!memberData['Last Name']?.trim()) {
|
||||
if (!memberData.last_name?.trim()) {
|
||||
throw new Error('Last Name is required');
|
||||
}
|
||||
if (!memberData.Email?.trim()) {
|
||||
if (!memberData.email?.trim()) {
|
||||
throw new Error('Email is required');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,146 @@
|
|||
<template>
|
||||
<div class="enhanced-phone-input">
|
||||
<PhoneInput
|
||||
v-model="phoneValue"
|
||||
:country-code="selectedCountryCode"
|
||||
:placeholder="placeholder || 'Enter phone number'"
|
||||
:preferred-countries="['US', 'FR', 'GB', 'DE', 'CA', 'AU', 'ES', 'IT', 'NL', 'BE']"
|
||||
:auto-format="true"
|
||||
:no-formatting-as-you-type="false"
|
||||
country-locale="en-US"
|
||||
@update="handlePhoneUpdate"
|
||||
@input="handlePhoneInput"
|
||||
class="phone-input-wrapper"
|
||||
>
|
||||
<template #input="{ inputValue, updateInputValue, placeholder: inputPlaceholder }">
|
||||
<v-text-field
|
||||
:model-value="inputValue"
|
||||
@update:model-value="updateInputValue"
|
||||
:placeholder="inputPlaceholder"
|
||||
:label="label"
|
||||
variant="outlined"
|
||||
:error="hasError"
|
||||
:error-messages="errorMessage"
|
||||
:rules="rules"
|
||||
class="phone-number-input"
|
||||
hide-details="auto"
|
||||
/>
|
||||
</template>
|
||||
</PhoneInput>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import PhoneInput from 'base-vue-phone-input';
|
||||
import type { Results as PhoneResults } from 'base-vue-phone-input';
|
||||
|
||||
interface Props {
|
||||
modelValue?: string;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
error?: boolean;
|
||||
errorMessage?: string;
|
||||
rules?: Array<(value: any) => boolean | string>;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:model-value', value: string): void;
|
||||
(e: 'phone-data', data: PhoneResults): void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: '',
|
||||
label: 'Phone Number',
|
||||
placeholder: 'Enter phone number',
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
rules: () => []
|
||||
});
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
// Local state
|
||||
const phoneValue = ref(props.modelValue);
|
||||
const selectedCountryCode = ref('US');
|
||||
const phoneData = ref<PhoneResults | null>(null);
|
||||
|
||||
// Computed properties
|
||||
const hasError = computed(() => props.error);
|
||||
|
||||
// Watch for external changes to modelValue
|
||||
watch(() => props.modelValue, (newValue) => {
|
||||
if (newValue !== phoneValue.value) {
|
||||
phoneValue.value = newValue;
|
||||
}
|
||||
});
|
||||
|
||||
// Handle phone input events
|
||||
const handlePhoneInput = (value: string) => {
|
||||
phoneValue.value = value;
|
||||
emit('update:model-value', value);
|
||||
};
|
||||
|
||||
const handlePhoneUpdate = (data: PhoneResults) => {
|
||||
phoneData.value = data;
|
||||
|
||||
// Update country code if available
|
||||
if (data.countryCode) {
|
||||
selectedCountryCode.value = data.countryCode;
|
||||
}
|
||||
|
||||
// Emit the formatted phone number
|
||||
const formattedNumber = data.formatInternational || data.e164 || phoneValue.value;
|
||||
if (formattedNumber !== phoneValue.value) {
|
||||
phoneValue.value = formattedNumber;
|
||||
emit('update:model-value', formattedNumber);
|
||||
}
|
||||
|
||||
// Emit phone data for parent component
|
||||
emit('phone-data', data);
|
||||
};
|
||||
|
||||
// Initialize with modelValue
|
||||
onMounted(() => {
|
||||
if (props.modelValue) {
|
||||
phoneValue.value = props.modelValue;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.enhanced-phone-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.phone-input-wrapper {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.country-select {
|
||||
flex: 0 0 120px;
|
||||
}
|
||||
|
||||
.phone-number-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.flag-emoji {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.country-code {
|
||||
font-size: 0.875rem;
|
||||
color: rgba(var(--v-theme-on-surface), 0.87);
|
||||
}
|
||||
|
||||
/* Ensure proper spacing in Vuetify forms */
|
||||
:deep(.v-input) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.v-input__details) {
|
||||
min-height: 20px;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue