Skip to content
Snippets Groups Projects
ShowBasicData.vue 14.4 KiB
Newer Older
    <PageHeader
      :title="t('navigation.show.basicData')"
      :lead="show.name"
      :editing-metadata="show"
    />
      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"
          edit-permissions="program.edit__show__name"
        >
          <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, disabled }"
          :label="t('show.fields.shortDescription')"
          :is-saving="shortDescription.isSaving"
          :errors="shortDescription.errors"
          class="tw-col-span-2"
          edit-permissions="program.edit__show__short_description"
          <AHTMLEditor
            :id="id"
            v-model="shortDescription.value"
            :disabled="disabled"
            @blur="shortDescription.save()"
          />
          v-slot="{ id, disabled }"
          :label="t('show.fields.description')"
          :is-saving="description.isSaving"
          :errors="description.errors"
          edit-permissions="program.edit__show__description"
          class="tw-col-span-2"
          custom-control
        >
          <AHTMLEditor
            :id="id"
            v-model="description.value"
            :disabled="disabled"
            @blur="shortDescription.save()"
          />
        </FormGroup>

        <div class="tw-flex tw-gap-6 tw-flex-wrap">
          <FormGroup
Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
            :label="t('show.fields.logoId')"
            :errors="logoId.errors"
            :is-saving="logoId.isSaving"
            class="tw-mb-0"
            custom-control
          >
            <div>
              <ImagePicker v-model="logoId.value" v-bind="attrs" />
            </div>
Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
            :label="t('show.fields.imageId')"
            :errors="imageId.errors"
            :is-saving="imageId.isSaving"
            custom-control
          >
            <div>
              <ImagePicker v-model="imageId.value" v-bind="attrs" />
            </div>
          </FormGroup>
        </div>
      </AFieldset>

      <AFieldset class="tw-bg-white" :title="t('show.section.content.title')">
        <FormGroup
Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
          :label="t('show.fields.categoryIds')"
          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>

Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
          :label="t('show.fields.topicIds')"
          custom-control
          :is-saving="topics.isSaving"
          :errors="topics.errors"
        >
          <ComboBoxSimple v-model="topics.value" :choices="topics.choices" />
        </FormGroup>

Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
          :label="t('show.fields.musicFocusIds')"
          custom-control
          :is-saving="musicFocuses.isSaving"
          :errors="musicFocuses.errors"
        >
          <ComboBoxSimple v-model="musicFocuses.value" :choices="musicFocuses.choices" />
        </FormGroup>

Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
          :label="t('show.fields.languageIds')"
          custom-control
          :is-saving="languages.isSaving"
          :errors="languages.errors"
        >
          <ComboBoxSimple v-model="languages.value" :choices="languages.choices" />
        </FormGroup>

Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
        <FormGroup
Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
          :label="t('show.fields.typeId')"
          :errors="type.errors"
          :is-saving="type.isSaving"
        >
          <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>
        </FormGroup>
      </AFieldset>

      <AFieldset class="tw-bg-white" :title="t('show.section.contact.title')">
Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
        <FormGroup
Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
          :label="t('show.fields.email')"
          :errors="email.errors"
          :is-saving="email.isSaving"
        >
          <input v-model="email.value" type="email" v-bind="attrs" @blur="email.save" />
        </FormGroup>

        <FormGroup
          :label="t('show.fields.links')"
          :is-saving="links.isSaving"
          :errors="links.errors.forField('links', '')"
          :has-error="links.errors.length > 0"
          custom-control
        >
          <ALinkCollectionEditor
            v-model="links.value"
            :error-lists="links.errors.siblings('links')"
            allow-add
            @save="links.save()"
Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
          :label="t('show.fields.hostIds')"
          custom-control
          :is-saving="hosts.isSaving"
          :errors="hosts.errors"
        >
          <ComboBoxSimple v-model="hosts.value" :choices="hosts.choices" />
        </FormGroup>

Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
          :label="t('show.fields.ownerIds')"
          class="tw-order-last"
          :is-saving="owners.isSaving"
          :errors="owners.errors"
        >
          <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"
Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
          :label="t('show.fields.fundingCategoryId')"
          :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>
Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
          :label="t('show.fields.cbaSeriesId')"
          :errors="cbaSeriesId.errors"
          :is-saving="cbaSeriesId.isSaving"
        >
          <input
            v-model="cbaSeriesId.value"
            type="text"
            inputmode="numeric"
            pattern="[0-9]+"
            v-bind="attrs"
            @blur="cbaSeriesId.save"
          />
Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
          :label="t('show.fields.predecessorId')"
          :errors="predecessor.errors"
          :is-saving="predecessor.isSaving"
        >
          <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>
        </FormGroup>

        <template v-if="authStore.isSuperuser">
          <FormGroup
Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
            :label="t('show.fields.internalNote')"
            :errors="internalNote.errors"
            class="md:tw-col-span-2 tw-order-last"
            :is-saving="internalNote.isSaving"
          >
            <textarea
              ref="internalNoteEl"
              v-model="internalNote.value"
              class="tw-min-h-[100px]"
              v-bind="attrs"
              @blur="internalNote.save"
            />
          </FormGroup>
        </template>
      </AFieldset>

      <AFieldset :title="t('show.section.media.title')" class="tw-bg-white">
        <FormGroup :errors="playlistId.errors">
          <APlaylistEditor
            :playlist="playlist"
            :show="show"
            :use-expert-mode="true"
            class="tw-max-w-3xl"
            @create="playlistId.value = $event.id"
          />
        </FormGroup>
      </AFieldset>

      <AHousekeeping :show="show" class="tw-col-span-full" />
    </div>
    <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 { useObjectFromStore } from '@rokoli/bnb/drf'
import { useTextareaAutosize } from '@vueuse/core'
import { computed } from 'vue'

import { useI18n } from '@/i18n'
import { Show } from '@/types'
import { 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'
import APlaylistEditor from '@/components/playlist/APlaylistEditor.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 playlistStore = usePlaylistStore()

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, show, 'logoId', { debounce: 0 })
const imageId = useAPIObjectFieldCopy(showStore, show, 'imageId', { debounce: 0 })
const owners = useRelationList(showStore, () => props.show, 'ownerIds', userStore, {
  sortBy: ['lastName', 'firstName', 'username', 'email'],
})

const { obj: playlist } = useObjectFromStore(() => props.show.defaultPlaylistId, playlistStore)
const playlistId = useAPIObjectFieldCopy(showStore, show, 'defaultPlaylistId', { debounce: 0 })

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),
})