Update glossary form with base components

* Create button layout to reuse across forms
* Create generic layout for forms to when create and edit items
pull/4789/head
Daniel Gayoso González 1 year ago
parent 3e827fe1ec
commit f67966af55
  1. 18
      assets/vue/components/basecomponents/BaseTextArea.vue
  2. 54
      assets/vue/components/basecomponents/BaseTextAreaWithVuelidate.vue
  3. 167
      assets/vue/components/glossary/GlossaryForm.vue
  4. 5
      assets/vue/components/layout/LayoutFormButtons.vue
  5. 12
      assets/vue/components/layout/LayoutFormGeneric.vue
  6. 5
      assets/vue/components/links/LinkForm.vue
  7. 25
      assets/vue/services/glossaryService.js
  8. 22
      assets/vue/views/glossary/GlossaryTermCreate.vue
  9. 25
      assets/vue/views/links/LinksCreate.vue

@ -9,11 +9,23 @@
type="text" type="text"
@update:model-value="$emit('update:modelValue', $event)" @update:model-value="$emit('update:modelValue', $event)"
/> />
<label v-t="label" :class="{ 'p-error': isInvalid }" :for="id" /> <label
v-t="label"
:class="{ 'p-error': isInvalid }"
:for="id"
/>
</div> </div>
<slot name="errors">
<small
v-if="isInvalid"
v-t="errorText"
class="p-error"
/>
</slot>
</div> </div>
</template> </template>
<script setup lang="ts">
<script setup>
import Textarea from "primevue/textarea" import Textarea from "primevue/textarea"
const props = defineProps({ const props = defineProps({
@ -41,7 +53,7 @@ const props = defineProps({
required: false, required: false,
default: false, default: false,
}, },
}); })
defineEmits(["update:modelValue"]) defineEmits(["update:modelValue"])
</script> </script>

@ -0,0 +1,54 @@
<template>
<BaseTextArea
:id="id"
:label="labelWithRequiredIfNeeded"
:model-value="modelValue"
:is-invalid="vuelidateProperty.$error"
@update:model-value="emit('update:modelValue', $event)"
>
<template #errors>
<p
v-for="error in vuelidateProperty.$errors"
:key="error.$uid"
class="mt-1 text-error"
>
{{ error.$message }}
</p>
</template>
</BaseTextArea>
</template>
<script setup>
import BaseTextArea from "./BaseTextArea.vue"
import { computed } from "vue"
const props = defineProps({
id: {
type: String,
require: true,
default: "",
},
label: {
type: String,
required: true,
default: "",
},
modelValue: {
type: String,
required: true,
},
vuelidateProperty: {
type: Object,
required: true,
},
})
const emit = defineEmits(["update:modelValue"])
const labelWithRequiredIfNeeded = computed(() => {
if (Object.hasOwn(props.vuelidateProperty, "required")) {
return `* ${props.label}`
}
return props.label
})
</script>

@ -1,64 +1,55 @@
<template> <template>
<div> <form class="mt-6 flex flex-col gap-2">
<form @submit.prevent="submitGlossaryForm" name="glossary" id="glossary"> <BaseInputTextWithVuelidate
<div class="field"> id="term-name"
<div class="p-float-label">
<input
v-model="formData.name" v-model="formData.name"
id="glossary_title" :vuelidate-property="v$.name"
name="name" :label="t('Term')"
type="text" />
class="p-inputtext p-component p-filled" <BaseTextAreaWithVuelidate
id="term-description"
v-model="formData.description"
:label="t('Description')"
:vuelidate-property="v$.description"
/> />
<label for="glossary_title"> <LayoutFormButtons>
<span class="form_required">*</span> <BaseButton
Term :label="t('Back')"
</label> type="black"
</div> icon="back"
</div> @click="emit('backPressed')"
<div class="field"> />
<div class="p-float-label"> <BaseButton
<textarea v-model="formData.description" id="description" name="description"></textarea> :label="t('Save term')"
type="success"
<label for="description"> icon="send"
<span class="form_required">*</span> @click="submitGlossaryForm"
Term definition />
</label> </LayoutFormButtons>
</div>
</div>
<div class="field 2">
<div class="8">
<label for="glossary_SubmitGlossary" class="h-4"> </label>
<button class="btn btn--primary" name="SubmitGlossary" type="submit" id="glossary_SubmitGlossary">
<em class="mdi mdi-plus"></em> Save term
</button>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<span class="form_required">*</span>
<small>Required field</small>
</div>
</div>
<input name="_qf__glossary" type="hidden" value="" id="glossary__qf__glossary" />
<input name="sec_token" type="hidden" value="1e7d47c276bfdfe308a79e1b71d58089" id="glossary_sec_token" />
</form> </form>
</div>
</template> </template>
<script setup> <script setup>
import axios from "axios"
import { ENTRYPOINT } from "../../config/entrypoint"
import { useRoute, useRouter } from "vue-router" import { useRoute, useRouter } from "vue-router"
import { useI18n } from "vue-i18n" import { useI18n } from "vue-i18n"
import { ref, onMounted } from "vue" import { onMounted, reactive, ref } from "vue"
import { RESOURCE_LINK_PUBLISHED } from "../resource_links/visibility" import { RESOURCE_LINK_PUBLISHED } from "../resource_links/visibility"
import LayoutFormButtons from "../layout/LayoutFormButtons.vue"
import BaseButton from "../basecomponents/BaseButton.vue"
import BaseInputTextWithVuelidate from "../basecomponents/BaseInputTextWithVuelidate.vue"
import { required } from "@vuelidate/validators"
import useVuelidate from "@vuelidate/core"
import BaseTextAreaWithVuelidate from "../basecomponents/BaseTextAreaWithVuelidate.vue"
import { useNotification } from "../../composables/notification"
import glossaryService from "../../services/glossaryService"
import { useCidReq } from "../../composables/cidReq"
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const { t } = useI18n() const { t } = useI18n()
const notification = useNotification()
const { sid, cid } = useCidReq()
const props = defineProps({ const props = defineProps({
termId: { termId: {
@ -67,82 +58,80 @@ const props = defineProps({
}, },
}) })
const emit = defineEmits(["backPressed"])
const parentResourceNodeId = ref(Number(route.params.node)) const parentResourceNodeId = ref(Number(route.params.node))
const resourceLinkList = ref( const resourceLinkList = ref(
JSON.stringify([ JSON.stringify([
{ {
sid: route.query.sid, sid,
cid: route.query.cid, cid,
visibility: RESOURCE_LINK_PUBLISHED, // visible by default visibility: RESOURCE_LINK_PUBLISHED, // visible by default
}, },
]) ])
) )
const formData = ref({ const formData = reactive({
name: "", name: "",
description: "", description: "",
}) })
const rules = {
name: { required },
description: { required },
}
const v$ = useVuelidate(rules, formData)
onMounted(() => { onMounted(() => {
fetchTerm() fetchTerm()
}) })
const fetchTerm = () => { const fetchTerm = async () => {
if (props.termId) { if (props.termId === null) {
axios return
.get(ENTRYPOINT + "glossaries/" + props.termId) }
.then((response) => { try {
const glossary = response.data const glossary = await glossaryService.getGlossaryTerm(props.termId)
formData.value.name = glossary.name formData.name = glossary.name
formData.value.description = glossary.description formData.description = glossary.description
}) } catch (error) {
.catch((error) => { console.error("Error glossary term:", error)
console.error("Error fetching link:", error) notification.showErrorNotification(t("Could not fetch glossary term"))
})
} }
} }
const submitGlossaryForm = () => { const submitGlossaryForm = async () => {
v$.value.$touch()
if (v$.value.$invalid) {
return
}
const postData = { const postData = {
name: formData.value.name, name: formData.name,
description: formData.value.description, description: formData.description,
parentResourceNodeId: parentResourceNodeId.value, parentResourceNodeId: parentResourceNodeId.value,
resourceLinkList: resourceLinkList.value, resourceLinkList: resourceLinkList.value,
sid: route.query.sid, sid: route.query.sid,
cid: route.query.cid, cid: route.query.cid,
} }
if (props.termId) { try {
const endpoint = `${ENTRYPOINT}glossaries/${props.termId}` if (props.linkId) {
axios await glossaryService.updateGlossaryTerm(props.linkId, postData)
.put(endpoint, postData)
.then((response) => {
console.log("Glossary updated:", response.data)
router.push({
name: "GlossaryList",
query: route.query,
})
})
.catch((error) => {
console.error("Error updating Glossary:", error)
})
} else { } else {
const endpoint = `${ENTRYPOINT}glossaries` await glossaryService.createGlossaryTerm(postData)
axios }
.post(endpoint, postData)
.then((response) => { notification.showSuccessNotification(t("Glossary term saved"))
console.log("Glossary created:", response.data)
router.push({ await router.push({
name: "GlossaryList", name: "GlossaryList",
query: route.query, query: route.query,
}) })
}) } catch (error) {
.catch((error) => { console.error("Error updating link:", error)
console.error("Error creating Glossary:", error) notification.showErrorNotification(t("Could not create glossary term"))
})
} }
} }
</script> </script>

@ -0,0 +1,5 @@
<template>
<div class="flex gap-4">
<slot></slot>
</div>
</template>

@ -0,0 +1,12 @@
<template>
<div>
<h2 class="text-h3 font-small text-gray-800 mb-4">
<slot name="header"></slot>
</h2>
<hr class="m-0 mb-4"/>
<slot></slot>
</div>
</template>
<script setup>
</script>

@ -43,7 +43,7 @@
option-value="value" option-value="value"
/> />
<div class="flex gap-4"> <LayoutFormButtons>
<BaseButton <BaseButton
:label="t('Back')" :label="t('Back')"
type="black" type="black"
@ -56,7 +56,7 @@
icon="send" icon="send"
@click="submitForm" @click="submitForm"
/> />
</div> </LayoutFormButtons>
</form> </form>
</template> </template>
@ -75,6 +75,7 @@ import BaseCheckbox from "../basecomponents/BaseCheckbox.vue";
import BaseTextArea from "../basecomponents/BaseTextArea.vue"; import BaseTextArea from "../basecomponents/BaseTextArea.vue";
import BaseSelect from "../basecomponents/BaseSelect.vue"; import BaseSelect from "../basecomponents/BaseSelect.vue";
import {useNotification} from "../../composables/notification"; import {useNotification} from "../../composables/notification";
import LayoutFormButtons from "../layout/LayoutFormButtons.vue";
const notification = useNotification() const notification = useNotification()
const {t} = useI18n() const {t} = useI18n()

@ -10,6 +10,31 @@ export default {
return response.data return response.data
}, },
/**
* @param {String|Number} termId
*/
getGlossaryTerm: async (termId) => {
const response = await axios.get(ENTRYPOINT + `glossaries/${termId}`)
return response.data
},
/**
* @param {Object} data
*/
createGlossaryTerm: async (data) => {
const response = await axios.post(ENTRYPOINT + `glossaries`, data)
return response.data
},
/**
* @param {String|Number} termId
* @param {Object} data
*/
updateGlossaryTerm: async (termId, data) => {
const response = await axios.put(ENTRYPOINT + `glossaries/${termId}`, data)
return response.data
},
/** /**
* @param {Object} data * @param {Object} data
*/ */

@ -1,22 +1,24 @@
<template> <template>
<div> <LayoutFormGeneric>
<div class="mb-4"> <template #header>
<button class="btn btn--secondary" @click="goBack">Back</button> <BaseIcon icon="plus" />
</div> {{ t("Add new glossary term") }}
<h1 class="text-h3 font-small text-gray-800 mb-4"> </template>
Add new glossary term
<hr /> <GlossaryForm @back-pressed="goBack" />
</h1> </LayoutFormGeneric>
<GlossaryForm />
</div>
</template> </template>
<script setup> <script setup>
import GlossaryForm from "../../components/glossary/GlossaryForm.vue" import GlossaryForm from "../../components/glossary/GlossaryForm.vue"
import { useRouter, useRoute } from "vue-router" import { useRouter, useRoute } from "vue-router"
import BaseIcon from "../../components/basecomponents/BaseIcon.vue"
import { useI18n } from "vue-i18n"
import LayoutFormGeneric from "../../components/layout/LayoutFormGeneric.vue"
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const { t } = useI18n()
const goBack = () => { const goBack = () => {
router.push({ router.push({

@ -1,23 +1,22 @@
<template> <template>
<div> <LayoutFormGeneric>
<h2 class="text-h3 font-small text-gray-800 mb-4"> <template #header>
<BaseIcon icon="link-add"/> <BaseIcon icon="link-add" />
{{ t('Add a link') }} {{ t("Add a link") }}
</h2> </template>
<hr class="m-0 mb-4"/>
<LinkForm <LinkForm @back-pressed="goBack" />
@back-pressed="goBack" </LayoutFormGeneric>
/>
</div>
</template> </template>
<script setup> <script setup>
import LinkForm from "../../components/links/LinkForm.vue" import LinkForm from "../../components/links/LinkForm.vue"
import { useRouter, useRoute } from "vue-router" import { useRoute, useRouter } from "vue-router"
import {useI18n} from "vue-i18n"; import { useI18n } from "vue-i18n"
import BaseIcon from "../../components/basecomponents/BaseIcon.vue" import BaseIcon from "../../components/basecomponents/BaseIcon.vue"
import LayoutFormGeneric from "../../components/layout/LayoutFormGeneric.vue"
const {t} = useI18n() const { t } = useI18n()
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()

Loading…
Cancel
Save