<template> <div> <PageHeader :title="t('navigation.show.basicData')" :lead="show.name" :editing-metadata="show" /> <div class="tw-grid tw-gap-6 tw-items-start tw-grid-cols-1 xl:tw-grid-cols-2 2xl:tw-grid-cols-3 tw-mb-6 tw-max-w-[1600px]" > <AFieldset class="tw-bg-white tw-col-span-full 2xl:tw-col-span-2" :title="t('show.section.basic.title')" > <FormGroup v-slot="attrs" :label="t('show.fields.name')" :is-saving="name.isSaving" :errors="name.errors" class="tw-col-span-2" > <input v-bind="attrs" v-model="name.value" type="text" @blur="name.save" /> <ADescription class="tw-text-xs"> <span>{{ t('show.slugDetail.title') }}: </span> <code class="tw-text-inherit tw-bg-gray-200 tw-px-2 tw-py-1 tw-rounded tw-text-gray-500" > {{ show.slug }} </code> <br /> <SafeHTML :html=" t('show.slugDetail.editRemark', { dangerZone: t('show.housekeeping.title'), dangerZoneId: 'danger-zone', }) " sanitize-preset="safe-html" /> </ADescription> </FormGroup> <FormGroup v-slot="{ id }" :label="t('show.fields.shortDescription')" :is-saving="shortDescription.isSaving" :errors="shortDescription.errors" class="tw-col-span-2" custom-control > <AHTMLEditor :id="id" v-model="shortDescription.value" @blur="shortDescription.save()" /> </FormGroup> <FormGroup v-slot="{ id }" :label="t('show.fields.description')" :is-saving="description.isSaving" :errors="description.errors" class="tw-col-span-2" custom-control > <AHTMLEditor :id="id" v-model="description.value" @blur="shortDescription.save()" /> </FormGroup> <div class="tw-flex tw-gap-6 tw-flex-wrap"> <FormGroup :label="t('showMeta.logo')" :errors="logoId.errors" :is-saving="logoId.isSaving" class="tw-mb-0" custom-control > <template #default="attrs"> <div> <ImagePicker v-model="logoId.value" v-bind="attrs" /> </div> </template> </FormGroup> <FormGroup :label="t('showMeta.image')" :errors="imageId.errors" :is-saving="imageId.isSaving" custom-control > <template #default="attrs"> <div> <ImagePicker v-model="imageId.value" v-bind="attrs" /> </div> </template> </FormGroup> </div> </AFieldset> <div class="tw-hidden 2xl:tw-block" /> <AFieldset class="tw-bg-white" :title="t('show.section.content.title')"> <FormGroup :label="t('showMeta.categories')" custom-control :is-saving="categories.isSaving" > <ComboBoxSimple v-model="categories.value" :choices="categories.choices"> <template #selected="{ deselect }"> <Tag v-for="category in categories.value" :key="category.id" removable @remove="deselect(category)" > <span> <span class="tw-block">{{ category.name }}</span> <span v-if="category.subtitle.trim()" class="tw-text-xs"> {{ category.subtitle }} </span> </span> </Tag> </template> </ComboBoxSimple> </FormGroup> <FormGroup :label="t('showMeta.topics')" custom-control :is-saving="topics.isSaving"> <ComboBoxSimple v-model="topics.value" :choices="topics.choices" /> </FormGroup> <FormGroup :label="t('showMeta.genres')" custom-control :is-saving="musicFocuses.isSaving"> <ComboBoxSimple v-model="musicFocuses.value" :choices="musicFocuses.choices" /> </FormGroup> <FormGroup :label="t('showMeta.languages')" custom-control :is-saving="languages.isSaving"> <ComboBoxSimple v-model="languages.value" :choices="languages.choices" /> </FormGroup> <FormGroup :label="t('showMeta.type')" :errors="type.errors" :is-saving="type.isSaving"> <template #default="attrs"> <select v-model="type.valueId" v-bind="attrs"> <option v-for="choice in type.choices" :key="choice.id" :value="choice.id" :label="choice.name" :disabled="!choice.isActive" /> </select> </template> </FormGroup> </AFieldset> <AFieldset class="tw-bg-white" :title="t('show.section.contact.title')"> <FormGroup :label="t('showMeta.email')" :errors="email.errors" :is-saving="email.isSaving"> <template #default="attrs"> <input v-model="email.value" type="email" v-bind="attrs" @blur="email.save" /> </template> </FormGroup> <FormGroup :label="t('show.fields.links')" :is-saving="links.isSaving" :errors="getFieldErrors(links.errors, 'links')" :has-error="links.errors.length > 0" custom-control > <ALinkCollectionEditor v-model="links.value" :error-lists="getTreeFieldChildrenErrorsList(links.errors, 'links')" allow-add @save="links.save()" /> </FormGroup> <FormGroup :label="t('showMeta.hosts')" custom-control :is-saving="hosts.isSaving"> <ComboBoxSimple v-model="hosts.value" :choices="hosts.choices" /> </FormGroup> <FormGroup :label="t('showMeta.owners')" class="tw-order-last" :is-saving="owners.isSaving"> <ComboBoxSimple v-model="owners.value" :disabled="!authStore.isSuperuser" :search-provider="searchUsers" > <template #default="{ choice, ...itemAttrs }"> <li v-bind="itemAttrs"> <UserPreview :user="choice as SteeringUser" transparent /> </li> </template> <template #selected="{ deselect }"> <template v-for="user in owners.value" :key="user.id"> <UserPreview :user="user" removable @remove="deselect(user)" /> </template> </template> </ComboBoxSimple> </FormGroup> </AFieldset> <AFieldset class="tw-bg-white" :title="t('show.section.administrative.title')"> <FormGroup v-slot="attrs" :label="`${t('showMeta.fundingCategory')} ${t('showMeta.fundingCategoryRtr')}`" :errors="fundingCategory.errors" :is-saving="fundingCategory.isSaving" > <select v-model="fundingCategory.valueId" v-bind="attrs"> <option v-for="choice in fundingCategory.choices" :key="choice.id" :value="choice.id" :label="choice.name" :disabled="!choice.isActive" /> </select> </FormGroup> <FormGroup :label="t('showMeta.cbaSeriesId')" :errors="cbaSeriesId.errors" :is-saving="cbaSeriesId.isSaving" > <template #default="attrs"> <input v-model="cbaSeriesId.value" type="text" inputmode="numeric" pattern="[0-9]+" v-bind="attrs" @blur="cbaSeriesId.save" /> </template> </FormGroup> <FormGroup :label="t('showMeta.predecessor')" :errors="predecessor.errors" :is-saving="predecessor.isSaving" > <template #default="attrs"> <select v-model="predecessor.valueId" v-bind="attrs"> <option v-for="choice in predecessor.choices" :key="choice.id" :value="choice.id" :label="sanitizeHTML(choice.name)" :disabled="!choice.isActive" /> </select> </template> </FormGroup> <template v-if="authStore.isSuperuser"> <FormGroup :label="t('showMeta.internalNote')" :errors="internalNote.errors" class="md:tw-col-span-2 tw-order-last" :is-saving="internalNote.isSaving" > <template #default="attrs"> <textarea ref="internalNoteEl" v-model="internalNote.value" class="tw-min-h-[100px]" v-bind="attrs" @blur="internalNote.save" /> </template> </FormGroup> </template> </AFieldset> <AHousekeeping :show="show" class="tw-col-span-full" /> </div> <hr /> <p class="tw-text-sm"> <ATimeEditInfo v-if="show.updatedAt" :edit-info="{ time: show.updatedAt, author: show.updatedBy }" type="modified" /> <br /> <ATimeEditInfo :edit-info="{ time: show.createdAt, author: show.createdBy }" type="created" /> </p> </div> </template> <script lang="ts" setup> import { useTextareaAutosize } from '@vueuse/core' import { computed } from 'vue' import { useI18n } from '@/i18n' import { Show } from '@/types' import { getTreeFieldChildrenErrorsList, getFieldErrors, useAPIObjectFieldCopy, useRelation, useRelationList, } from '@/form' import { useAuthStore, useCategoryStore, useFundingCategoryStore, useHostStore, useLanguageStore, useMusicFocusStore, useShowStore, useTopicStore, useTypeStore, useUserStore, } from '@/stores' import { useBreadcrumbs } from '@/stores/nav' import { SteeringUser } from '@/stores/auth' import { sanitizeHTML } from '@/util' import PageHeader from '@/components/PageHeader.vue' import ATimeEditInfo from '@/components/generic/ATimeEditInfo.vue' import FormGroup from '@/components/generic/FormGroup.vue' import AHousekeeping from '@/components/shows/AHousekeeping.vue' import ADescription from '@/components/generic/ADescription.vue' import SafeHTML from '@/components/generic/SafeHTML' import ALinkCollectionEditor from '@/components/generic/ALinkCollectionEditor.vue' import AHTMLEditor from '@/components/generic/AHTMLEditor.vue' import ComboBoxSimple from '@/components/ComboBoxSimple.vue' import UserPreview from '@/components/UserPreview.vue' import Tag from '@/components/generic/Tag.vue' import ImagePicker from '@/components/images/ImagePicker.vue' import AFieldset from '@/components/generic/AFieldset.vue' const props = defineProps<{ show: Show }>() const { t } = useI18n() const authStore = useAuthStore() const userStore = useUserStore() const showStore = useShowStore() const typeStore = useTypeStore() const categoryStore = useCategoryStore() const topicStore = useTopicStore() const musicFocusStore = useMusicFocusStore() const languageStore = useLanguageStore() const hostStore = useHostStore() const fundingCategoryStore = useFundingCategoryStore() const show = computed(() => props.show) const name = useAPIObjectFieldCopy(showStore, show, 'name', { debounce: 2 }) const shortDescription = useAPIObjectFieldCopy(showStore, show, 'shortDescription', { debounce: 2 }) const description = useAPIObjectFieldCopy(showStore, show, 'description', { debounce: 2 }) const links = useAPIObjectFieldCopy(showStore, show, 'links', { debounce: 2 }) const email = useAPIObjectFieldCopy(showStore, show, 'email', { debounce: 2 }) const cbaSeriesId = useAPIObjectFieldCopy(showStore, show, 'cbaSeriesId', { debounce: 2 }) const internalNote = useAPIObjectFieldCopy(showStore, show, 'internalNote', { debounce: 2 }) const type = useRelation(showStore, show, 'typeId', typeStore) const fundingCategory = useRelation(showStore, show, 'fundingCategoryId', fundingCategoryStore) const predecessor = useRelation(showStore, show, 'predecessorId', showStore) const categories = useRelationList(showStore, show, 'categoryIds', categoryStore) const topics = useRelationList(showStore, show, 'topicIds', topicStore) const languages = useRelationList(showStore, show, 'languageIds', languageStore) const hosts = useRelationList(showStore, show, 'hostIds', hostStore) const musicFocuses = useRelationList(showStore, show, 'musicFocusIds', musicFocusStore) const logoId = useAPIObjectFieldCopy(showStore, () => props.show, 'logoId', { debounce: 0 }) const imageId = useAPIObjectFieldCopy(showStore, () => props.show, 'imageId', { debounce: 0 }) const owners = useRelationList(showStore, () => props.show, 'ownerIds', userStore, { sortBy: ['lastName', 'firstName', 'username', 'email'], }) function searchUsers(query: string, signal: AbortSignal) { return userStore.list({ query: new URLSearchParams({ search: query }), requestInit: { signal }, }) } useBreadcrumbs(() => [ { title: t('navigation.shows'), route: { name: 'shows' } }, { title: props.show.name, route: { name: 'show', params: { showId: props.show.id.toString() } } }, t('navigation.show.basicData'), ]) const { textarea: internalNoteEl } = useTextareaAutosize({ input: computed(() => internalNote.value), }) </script>