telephone updates
Build And Push Image / docker (push) Successful in 2m52s Details

This commit is contained in:
Matt 2025-08-07 22:13:05 +02:00
parent 13fa95a9a2
commit 024eca02ac
2 changed files with 237 additions and 76 deletions

View File

@ -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');
}

View File

@ -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>