11<template >
22 <div class =" main-content" >
3- <div class =" header" >
4- <div >
5- <FeatherBackButton
6- data-test =" back-button"
7- @click =" handleCancel(store.selectedSource?.id)"
8- >
9- Go Back
10- </FeatherBackButton >
3+ <div class =" title" >
4+ <div class =" header" >
5+ <div >
6+ <FeatherBackButton
7+ data-test =" back-button"
8+ @click =" handleCancel(store.selectedSource?.id)"
9+ >
10+ Go Back
11+ </FeatherBackButton >
12+ </div >
13+ <div >
14+ <h3 >
15+ {{ store.eventModificationState.isEditMode === CreateEditMode.Create ? 'Create New Event Configuration' :
16+ 'Edit Event Configuration Details' }}
17+ </h3 >
18+ </div >
1119 </div >
12- <div >
13- <h3 >
14- {{ store.eventModificationState.isEditMode === CreateEditMode.Create ? 'Create New Event Configuration' :
15- 'Edit Event Configuration Details' }}
16- </h3 >
20+ <div class =" action" >
21+ <FeatherButton
22+ primary
23+ @click =" showSourceCreationDialog"
24+ data-test =" create-new-event-source-button"
25+ :disabled =" store.selectedSource?.name && store.selectedSource?.id ? true : false"
26+ >
27+ Create New Event Source
28+ </FeatherButton >
1729 </div >
1830 </div >
1931 <div class =" spacer" ></div >
2234 <div class =" spacer" ></div >
2335 <FeatherAutocomplete
2436 class =" my-autocomplete"
25- :disabled =" store.selectedSource?.name ? true : false"
37+ :disabled =" store.selectedSource?.name && store.selectedSource?.id ? true : false"
2638 :model-value =" selectedSource"
2739 @update:model-value =" (item: any) => setSelectedSource(item)"
2840 label =" Source Name"
192204 </div >
193205 </div >
194206 </div >
207+ <FeatherDialog
208+ v-model =" sourceCreationDialogState"
209+ :labels =" labels"
210+ hide-close
211+ @hidden =" handleSourceCreationCancel"
212+ >
213+ <div class =" modal-body-form" >
214+ <div >
215+ <FeatherInput
216+ label =" Event Configuration Source Name"
217+ v-model =" configName"
218+ :error =" sourceCreationErrors?.name"
219+ data-test =" source-name"
220+ />
221+ </div >
222+ <div >
223+ <FeatherInput
224+ label =" Vendor"
225+ v-model =" vendor"
226+ :error =" sourceCreationErrors?.vendor"
227+ data-test =" vendor"
228+ />
229+ </div >
230+ </div >
231+ <template v-slot :footer >
232+ <FeatherButton
233+ @click =" handleSourceCreationCancel"
234+ data-test =" cancel-source-button"
235+ >
236+ Cancel
237+ </FeatherButton >
238+ <FeatherButton
239+ primary
240+ @click =" handleSourceCreationSave"
241+ :disabled =" Object.keys(sourceCreationErrors || {}).length > 0"
242+ data-test =" create-source-button"
243+ >
244+ Create Source
245+ </FeatherButton >
246+ </template >
247+ </FeatherDialog >
195248 </div >
196249</template >
197250
@@ -205,6 +258,7 @@ import { EventConfigEvent, EventFormErrors } from '@/types/eventConfig'
205258import { FeatherAutocomplete , IAutocompleteItemType } from ' @featherds/autocomplete'
206259import { FeatherBackButton } from ' @featherds/back-button'
207260import { FeatherButton } from ' @featherds/button'
261+ import { FeatherDialog } from ' @featherds/dialog'
208262import { FeatherIcon } from ' @featherds/icon'
209263import MoreVert from ' @featherds/icon/navigation/MoreVert'
210264import { FeatherInput } from ' @featherds/input'
@@ -217,7 +271,6 @@ import { validateEvent } from './eventValidator'
217271import MaskElements from ' ./MaskElements.vue'
218272import MaskVarbinds from ' ./MaskVarbinds.vue'
219273import VarbindsDecode from ' ./VarbindsDecode.vue'
220- import { mapEventConfigSourceFromServer } from ' @/mappers/eventConfig.mapper'
221274
222275const loading = ref (false )
223276const timeout = ref <number >(- 1 )
@@ -238,6 +291,9 @@ const snackbar = useSnackbar()
238291const destination = ref <ISelectItemType >({ _text: ' ' , _value: ' ' })
239292const severity = ref <ISelectItemType >({ _text: ' ' , _value: ' ' })
240293const alarmType = ref <ISelectItemType >({ _text: ' ' , _value: ' ' })
294+ const configName = ref (' ' )
295+ const vendor = ref (' ' )
296+ const sourceCreationDialogState = ref (false )
241297const maskElements = ref <Array <{ name: ISelectItemType ; value: string }>>([
242298 { name: { _text: ' ' , _value: ' ' }, value: ' ' }
243299])
@@ -249,6 +305,22 @@ const varbinds = ref<Array<{ index: string; value: string, type: ISelectItemType
249305 { index: ' 0' , value: ' ' , type: { _text: MaskVarbindsTypeText .vbNumber , _value: MaskVarbindsTypeValue .vbNumber } }
250306])
251307const varbindsDecode = ref <Array <{ parmId: string ; decode: Array <{ key: string ; value: string }> }>>([])
308+ const labels = {
309+ title: ' Create New Event Source'
310+ }
311+ const sourceCreationErrors = computed (() => {
312+ let error: any = {}
313+ if (configName .value .trim () === ' ' ) {
314+ error .name = ' Configuration name is required.'
315+ }
316+ if (vendor .value .trim () === ' ' ) {
317+ error .vendor = ' Vendor is required.'
318+ }
319+ if (vendor .value && vendor .value .length > 128 ) {
320+ error .vendor = ' Vendor must be less than 128 characters.'
321+ }
322+ return Object .keys (error ).length > 0 ? error : null
323+ })
252324
253325const xmlContent = computed (() => {
254326 return vkbeautify .xml (
@@ -541,27 +613,14 @@ const handleSaveEvent = async () => {
541613 }
542614
543615 if (selectedSource .value ?._value === - 1 ) {
544- snackbar .showSnackBar ({ msg: ' Source is required ' , error: true })
616+ snackbar .showSnackBar ({ msg: ' No source selected. Please select a source from the dropdown or create a new one. ' , error: true })
545617 return
546618 }
547619
548620 try {
549- if (selectedSource .value ?._value === 0 ) {
550- const response = await addEventConfigSource (
551- selectedSource .value ?._text as string ,
552- selectedSource .value ?._text as string ,
553- ' '
554- )
555- await store .fetchSourceById (String (mapEventConfigSourceFromServer (response ).id ))
556- selectedSource .value = {
557- _text: store .selectedSource ?.name || ' ' ,
558- _value: store .selectedSource ?.id || - 1
559- }
560- }
561621 const sourceId = selectedSource .value ?._value as number
562-
563622 if (! sourceId ) {
564- snackbar .showSnackBar ({ msg: ' Source is required ' , error: true })
623+ snackbar .showSnackBar ({ msg: ' No source selected. Please select a source from the dropdown or create a new one. ' , error: true })
565624 return
566625 }
567626
@@ -606,6 +665,59 @@ const handleCancel = (id?: number) => {
606665 }
607666}
608667
668+ const showSourceCreationDialog = () => {
669+ configName .value = ' '
670+ vendor .value = ' '
671+ sourceCreationDialogState .value = true
672+ }
673+
674+ const handleSourceCreationSave = async () => {
675+ try {
676+ const response = await addEventConfigSource (
677+ configName .value ,
678+ vendor .value ,
679+ ' '
680+ )
681+ if (response && typeof response === ' object' && response .status === 201 ) {
682+ // Success: response contains { id, name, fileOrder, status: 201 }
683+ await eventConfigStore .fetchAllSourcesNames ()
684+ selectedSource .value = { _text: response .name , _value: response .id }
685+ configName .value = ' '
686+ vendor .value = ' '
687+ sourceCreationDialogState .value = false
688+ } else if (response === 409 ) {
689+ // Conflict: duplicate name
690+ snackbar .showSnackBar ({
691+ msg: ' An event configuration source with this name already exists.' ,
692+ error: true
693+ })
694+ } else if (response === 400 ) {
695+ // Bad request: validation error
696+ snackbar .showSnackBar ({
697+ msg: ' Invalid request. Please check your input and try again.' ,
698+ error: true
699+ })
700+ } else {
701+ // 500 or any other error
702+ snackbar .showSnackBar ({
703+ msg: ' Failed to create event configuration source. Please try again.' ,
704+ error: true
705+ })
706+ }
707+ } catch (error ) {
708+ snackbar .showSnackBar ({
709+ msg: ' Failed to create event configuration source. Please try again.' ,
710+ error: true
711+ })
712+ }
713+ }
714+
715+ const handleSourceCreationCancel = () => {
716+ configName .value = ' '
717+ vendor .value = ' '
718+ sourceCreationDialogState .value = false
719+ }
720+
609721watchEffect (() => {
610722 const currentErrors = validateEvent (
611723 eventUei .value ,
@@ -639,20 +751,9 @@ const search = (query: string) => {
639751 loading .value = true
640752 clearTimeout (timeout .value )
641753 timeout .value = window .setTimeout (() => {
642- const list = eventConfigStore .uploadedSources || []
643- const filtered = list
754+ results .value = eventConfigStore .uploadedSources
644755 .filter ((s ) => s .name .toLowerCase ().includes (query .toLowerCase ()))
645756 .map ((x ) => ({ _text: x .name , _value: x .id }))
646-
647- // Check if there's an exact match
648- const hasExactMatch = filtered .some ((item ) => item ._text .toLowerCase () === query .toLowerCase ())
649-
650- // Add fallback option only if no exact match exists and query is not empty
651- if (! hasExactMatch && query .trim ().length > 0 ) {
652- results .value = [... filtered , { _text: query .trim (), _value: 0 }]
653- } else {
654- results .value = filtered
655- }
656757 loading .value = false
657758 }, 500 )
658759}
@@ -674,10 +775,16 @@ onMounted(async () => {
674775 border-radius : 8px ;
675776 background-color : #ffffff ;
676777
677- .header {
778+ .title {
678779 display : flex ;
679780 align-items : center ;
680- gap : 20px ;
781+ justify-content : space-between ;
782+
783+ .header {
784+ display : flex ;
785+ align-items : center ;
786+ gap : 20px ;
787+ }
681788 }
682789
683790 .basic-info {
0 commit comments