66
77 <!-- Edit/Create page -->
88 <div v-else >
9- <BreadcrumbNav v-if =" props.mode === 'EDIT'" :crumbs =" [ { label: t('nav.groups'), to: '/app/groups' }, { label: name, to:'/app/groups/' + props.id }, { label: t('common.edit') } ]" />
9+ <BreadcrumbNav v-if =" props.mode === 'EDIT'" :crumbs =" [ { label: t('nav.groups'), to: '/app/groups' }, { label: data. name, to:'/app/groups/' + props.id }, { label: t('common.edit') } ]" />
1010 <BreadcrumbNav v-else :crumbs =" [ { label: t('nav.groups'), to: '/app/groups' }, { label: t('common.create') } ]" />
1111 <div class =" -my-2 -mx-4 sm:-mx-6 lg:-mx-8 overflow-hidden" >
1212 <div class =" py-2 align-middle inline-block min-w-full px-4 sm:px-6 lg:px-8" >
2121 <!-- Profile Picture Preview -->
2222 <div class =" flex flex-col items-center gap-4 mb-8" >
2323 <div class =" relative w-32 h-32" >
24- <img v-if =" isValidImageUrl" :src =" pictureUrl" class =" w-full h-full rounded-full object-cover border border-gray-300" :alt =" t('groupEditCreate.profilePicture')" />
24+ <img v-if =" isValidImageUrl" :src =" data. pictureUrl" class =" w-full h-full rounded-full object-cover border border-gray-300" :alt =" t('groupEditCreate.profilePicture')" />
2525 <div v-else class =" w-full h-full rounded-full bg-gray-100 flex items-center justify-center text-gray-400" >
2626 <UserGroupIcon class =" w-12 h-12" />
2727 </div >
3737 </label >
3838 <div class =" mt-1 md:mt-0 md:col-span-2 lg:col-span-1" >
3939 <div class =" relative" >
40- <input id =" pictureUrl" v-model =" pictureUrl" type =" url" :class =" [errors.pictureUrl ? 'border-red-300 focus:border-red-500 focus:ring-red-500' : 'border-gray-300 focus:ring-primary focus:border-primary', 'block w-full max-w-md shadow-sm sm:text-sm rounded-md pr-10']" />
41- <button v-if =" pictureUrl" type =" button" class =" absolute inset-y-0 right-0 flex items-center px-3 text-gray-400 hover:text-gray-600 focus:outline-none" :aria-label =" t('groupEditCreate.removePicture')" @click =" removePicture" >
40+ <input id =" pictureUrl" v-model =" data. pictureUrl" type =" url" :class =" [errors.pictureUrl ? 'border-red-300 focus:border-red-500 focus:ring-red-500' : 'border-gray-300 focus:ring-primary focus:border-primary', 'block w-full max-w-md shadow-sm sm:text-sm rounded-md pr-10']" />
41+ <button v-if =" data. pictureUrl" type =" button" class =" absolute inset-y-0 right-0 flex items-center px-3 text-gray-400 hover:text-gray-600 focus:outline-none" :aria-label =" t('groupEditCreate.removePicture')" @click =" removePicture" >
4242 <TrashIcon class =" w-5 h-5 text-gray-600" />
4343 </button >
4444 </div >
5252 {{ t('groupEditCreate.name') }}
5353 </label >
5454 <div class =" mt-1 md:mt-0 md:col-span-2 lg:col-span-1" >
55- <input id =" name" v-model =" name" type =" text" :class =" [errors.name ? 'border-red-300 focus:border-red-500 focus:ring-red-500' : 'border-gray-300 focus:ring-primary focus:border-primary', 'block w-full max-w-md shadow-sm sm:text-sm rounded-md']" required />
55+ <input id =" name" v-model =" data. name" type =" text" :class =" [errors.name ? 'border-red-300 focus:border-red-500 focus:ring-red-500' : 'border-gray-300 focus:ring-primary focus:border-primary', 'block w-full max-w-md shadow-sm sm:text-sm rounded-md']" required />
5656 <p v-if =" errors.name" class =" mt-1 text-sm text-red-600" >{{ errors.name }}</p >
5757 </div >
5858 </div >
9191
9292<script setup lang="ts">
9393import { ExclamationTriangleIcon , TrashIcon , UserGroupIcon } from ' @heroicons/vue/24/outline' ;
94- import { computed , onMounted , ref , watch } from ' vue' ;
94+ import { computed , onMounted , reactive , ref , shallowRef , watch } from ' vue' ;
9595import { useI18n } from ' vue-i18n' ;
9696import { useRouter } from ' vue-router' ;
97- import backend , { isAxiosError } from ' ../../common/backend' ;
97+ import backend , { GroupDto , isAxiosError } from ' ../../common/backend' ;
9898import { FormValidator } from ' ../../common/formvalidator' ;
9999import { debounce } from ' ../../common/util' ;
100100import BreadcrumbNav from ' ../BreadcrumbNav.vue' ;
@@ -107,77 +107,65 @@ const props = defineProps<{
107107 mode: ' EDIT' ,
108108}>();
109109
110- interface GroupData {
111- id? : string ;
112- name: string ;
113- pictureUrl? : string ;
114- }
115-
116- const initialGroupData = ref <GroupData >({ name: ' ' , pictureUrl: ' ' });
110+ type EditableGroupData = Pick <GroupDto , ' name' | ' pictureUrl' >;
111+ const initialData = shallowRef <EditableGroupData >({ name: ' ' , pictureUrl: ' ' });
112+ const data = reactive <EditableGroupData >(initialData .value );
117113
118114const groupDataHasUnsavedChanges = computed (() => {
119- return (
120- initialGroupData .value .name !== name .value ||
121- initialGroupData .value .pictureUrl !== pictureUrl .value
122- );
115+ return data .name !== initialData .value .name
116+ || data .pictureUrl !== initialData .value .pictureUrl ;
123117});
124118
125119function resetGroupData() {
126- name . value = initialGroupData .value .name ;
127- pictureUrl . value = initialGroupData .value .pictureUrl ?? ' ' ;
120+ data . name = initialData .value .name ;
121+ data . pictureUrl = initialData .value .pictureUrl ;
128122}
129123
130124const { t } = useI18n ({ useScope: ' global' });
131125const router = useRouter ();
132-
133126const loading = ref (true );
134- const name = ref <string >(' ' );
135-
136127const errors = ref <Record <string , string >>({});
137128const processing = ref (false );
138129const groupSaved = ref (false );
139130const onSaveError = ref <Error | null >(null );
140131const debouncedGroupSaved = debounce (() => groupSaved .value = false , 2000 );
141-
142- const pictureUrl = ref <string >(' ' );
143132const isValidImageUrl = ref <boolean >(false );
144133
145- watch (pictureUrl ,
134+ watch (() => data . pictureUrl ,
146135 async (newUrl ) => {
147136 isValidImageUrl .value = await FormValidator .validateImageUrl (newUrl );
148137 },
149138 { immediate: true }
150139);
151140
152141onMounted (async () => {
142+ loading .value = true ;
153143 if (props .mode === ' EDIT' ) {
154144 try {
155- const group = await backend .groups .getGroup (props .id );
156- name .value = group .name ;
157- pictureUrl .value = group .pictureUrl ?? ' ' ;
158- initialGroupData .value = {
159- id: group .id ,
160- name: group .name ,
161- pictureUrl: group .pictureUrl ?? ' '
162- };
145+ initialData .value = await backend .groups .getGroup (props .id );
163146 } catch (error ) {
164147 console .error (' Failed to fetch group:' , error );
148+ } finally {
149+ loading .value = false ;
165150 }
166151 } else {
167- name .value = ' ' ;
168- pictureUrl .value = ' ' ;
152+ initialData .value = {
153+ name: ' ' ,
154+ pictureUrl: ' '
155+ };
156+ loading .value = false ;
169157 }
170- loading . value = false ;
158+ resetGroupData () ;
171159});
172160
173161function removePicture() {
174- pictureUrl . value = ' ' ;
162+ data . pictureUrl = undefined ;
175163}
176164
177165function validateForm() {
178166 const result = FormValidator .validateGroup ({
179- name: name . value ,
180- pictureUrl: pictureUrl . value ,
167+ name: data . name ,
168+ pictureUrl: data . pictureUrl ,
181169 isValidImageUrl: isValidImageUrl .value
182170 });
183171
@@ -193,27 +181,20 @@ async function onSubmit() {
193181 processing .value = true ;
194182 onSaveError .value = null ;
195183
196- const trimmedName = name . value .trim ();
184+ data . name = data . name .trim ();
197185
198186 try {
199187 if (props .mode === ' EDIT' ) {
200- await backend .groups .updateGroup (props .id , {
201- name: trimmedName ,
202- pictureUrl: pictureUrl .value || undefined
203- });
188+ await backend .groups .updateGroup (props .id , data );
204189 } else {
205- await backend .groups .createGroup ({
206- name: trimmedName ,
207- pictureUrl: pictureUrl .value || undefined
208- });
190+ await backend .groups .createGroup (data );
209191 }
210192
211193 // Update initial data to match saved state
212- initialGroupData .value = {
213- name: trimmedName ,
214- pictureUrl: pictureUrl . value
194+ initialData .value = {
195+ name: data . name ,
196+ pictureUrl: data . pictureUrl
215197 };
216- name .value = trimmedName ;
217198
218199 // Show saved success state
219200 groupSaved .value = true ;
0 commit comments