feat(FE): spot

This commit is contained in:
Michael 2025-07-04 00:15:58 +02:00
parent 7eadbe16f5
commit 5af0b32634
18 changed files with 162 additions and 52 deletions

View file

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import {computed, onActivated, onDeactivated, ref} from "vue"; import {computed, onActivated, onDeactivated, onMounted, ref} from "vue";
import {useAuthStore} from "@/stores/auth.store.ts"; import {useAuthStore} from "@/stores/auth.store.ts";
import {deleteShow} from "@models/show/show.ts"; import {deleteShow} from "@models/show/show.ts";
import {baseShowInstance, deleteShowInstance, getShowInstances} from "@models/show/showInstance.ts"; import {baseShowInstance, deleteShowInstance, getShowInstances} from "@models/show/showInstance.ts";
@ -76,7 +76,7 @@ const goBack = async () => {
// so reducing the network calls sent // so reducing the network calls sent
// That requires the handling of the context menu // That requires the handling of the context menu
// and the show/instance id in a different way though // and the show/instance id in a different way though
onActivated(async () => { onMounted(async () => {
await triggerFetchShowInstances(new Date()); await triggerFetchShowInstances(new Date());
intervalId = setInterval(async () => { intervalId = setInterval(async () => {
if (!isRunning.value) { if (!isRunning.value) {

View file

@ -3,7 +3,7 @@ import {playlist_page} from "@/composables/content/playlist_page.ts";
import Table from "@/components/content/partials/Table.vue"; import Table from "@/components/content/partials/Table.vue";
import PlaylistEditor from "@/components/content/partials/PlaylistEditor.vue"; import PlaylistEditor from "@/components/content/partials/PlaylistEditor.vue";
import ConfirmDelete from "@/components/content/partials/dialogs/ConfirmDelete.vue"; import ConfirmDelete from "@/components/content/partials/dialogs/ConfirmDelete.vue";
import {reactive, ref, watch} from "vue"; import {onBeforeMount, onMounted, type PropType, reactive, ref, watch} from "vue";
import {usePlaylistStore} from "@/stores/playlist.store.ts"; import {usePlaylistStore} from "@/stores/playlist.store.ts";
import {baseSmartBlock} from "@models/smartblock/smartblock.ts"; import {baseSmartBlock} from "@models/smartblock/smartblock.ts";
import {basePlaylist} from "@models/playlist/playlist.ts"; import {basePlaylist} from "@models/playlist/playlist.ts";
@ -12,6 +12,16 @@ const playlistStore = usePlaylistStore();
const { items, listData, headers, selected, loading, search, getItems, editItem, deleteItem } = playlist_page(); const { items, listData, headers, selected, loading, search, getItems, editItem, deleteItem } = playlist_page();
// Props and data
const props = defineProps({
showType: {
type: String as PropType<'spot' | null>,
required: false,
validator: (value: string) => ['spot'].includes(value),
default: null,
},
});
const itemEdited = ref({ const itemEdited = ref({
id: null id: null
}); });
@ -37,10 +47,11 @@ const edit = (item) => {
item = basePlaylist(); item = basePlaylist();
} }
playlistStore.loadPlaylist(item); playlistStore.loadPlaylist(item);
playlistStore.currentPlaylist.playlist_type = props.showType;
itemEdited.value = item; itemEdited.value = item;
} }
const save = (item) => { const save = (item) => {5
if (item.name === '') { if (item.name === '') {
//Check required fields //Check required fields
console.log('error!') console.log('error!')
@ -91,7 +102,6 @@ const resetItemEdited = () => {
watch(search, (newValue, oldValue) => { watch(search, (newValue, oldValue) => {
getItems(listData) getItems(listData)
}) })
</script> </script>
<template> <template>

View file

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import {reactive, ref, watch} from "vue"; import {onActivated, onBeforeMount, onMounted, type PropType, reactive, ref, watch} from "vue";
import Table from "@partials/Table.vue"; import Table from "@partials/Table.vue";
import ConfirmDelete from "@partials/dialogs/ConfirmDelete.vue"; import ConfirmDelete from "@partials/dialogs/ConfirmDelete.vue";
import {show_page} from "@/composables/content/show/show_page.ts"; import {show_page} from "@/composables/content/show/show_page.ts";
@ -8,6 +8,16 @@ import {baseShow, type Show} from "@models/show/show";
const {items, listData, headers, selected, loading, search, getItems, editItem, deleteItem} = show_page() const {items, listData, headers, selected, loading, search, getItems, editItem, deleteItem} = show_page()
// Props and data
const props = defineProps({
showType: {
type: String as PropType<'show' | 'spot'>,
required: true,
validator: (value: string) => ['show', 'spot'].includes(value),
},
});
const showCreateEditMode = ref(false); const showCreateEditMode = ref(false);
let showSelected = ref<Number | null>(null); let showSelected = ref<Number | null>(null);
@ -23,6 +33,7 @@ const dialog = reactive({
text: '' text: ''
}) })
// Funcs
const openDialog = (type, title: string = '', text: string = '') => { const openDialog = (type, title: string = '', text: string = '') => {
dialog.open = true dialog.open = true
dialog.type = type dialog.type = type
@ -46,8 +57,8 @@ const saveItem = (item) => {
} }
const cancel = (item) => { const cancel = (item) => {
let deleteMessage = 'Vuoi cancellare lo show selezionato?' let deleteMessage = `Vuoi cancellare lo ${props.showType} selezionato?`
if(bulk.value.state) deleteMessage = 'Vuoi cancellare gli show selezionati?' if (bulk.value.state) deleteMessage = `Vuoi cancellare gli ${props.showType} selezionati?`
bulk.value.items = item bulk.value.items = item
showSelected.value = item?.id showSelected.value = item?.id
openDialog( openDialog(
@ -59,7 +70,7 @@ const cancel = (item) => {
const confirmDelete = (confirm) => { const confirmDelete = (confirm) => {
if (confirm) { if (confirm) {
const showId = showSelected.value == 0 ? null : showSelected.value ; const showId = showSelected.value == 0 ? null : showSelected.value;
deleteItem(showId) deleteItem(showId)
} }
closeDialog() closeDialog()
@ -78,19 +89,23 @@ const resetItemEdited = () => {
showSelected.value = null showSelected.value = null
} }
watch(search, (newValue, oldValue) => {
const options = {...listData};
getItems(options)
})
const goBack = () => { const goBack = () => {
showCreateEditMode.value = false showCreateEditMode.value = false
showSelected.value = null showSelected.value = null
} }
onBeforeMount(() => {
listData.show_type = props.showType
})
watch(search, (newValue, oldValue) => {
const options = {...listData};
getItems(options)
})
</script> </script>
<template> <template>
<ShowForm v-if="showCreateEditMode" :showId="showSelected" @go-back="goBack"/> <ShowForm v-if="showCreateEditMode" :showId="showSelected" :showType="props.showType" @go-back="goBack"/>
<Table <Table
v-else v-else
:headers="headers" :headers="headers"
@ -109,7 +124,7 @@ const goBack = () => {
> >
<template v-slot:header-buttons> <template v-slot:header-buttons>
<v-btn color="primary" @click="create"> <v-btn color="primary" @click="create">
Crea una nuova trasmissione <span>Crea un nuovo {{ props.showType }} </span>
</v-btn> </v-btn>
</template> </template>
<template v-slot:dialog> <template v-slot:dialog>

View file

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import Table from "@/components/content/partials/Table.vue"; import Table from "@/components/content/partials/Table.vue";
import ConfirmDelete from "@/components/content/partials/dialogs/ConfirmDelete.vue"; import ConfirmDelete from "@/components/content/partials/dialogs/ConfirmDelete.vue";
import {onBeforeMount, reactive, ref, watch} from "vue"; import {onBeforeMount, type PropType, reactive, ref, watch} from "vue";
import {smartblock_page} from "@/composables/content/smartblock_page.ts"; import {smartblock_page} from "@/composables/content/smartblock_page.ts";
import SmartBlockEditor from "@partials/SmartBlockEditor.vue"; import SmartBlockEditor from "@partials/SmartBlockEditor.vue";
import {useSmartBlockStore} from "@/stores/smartblock.store.ts"; import {useSmartBlockStore} from "@/stores/smartblock.store.ts";
@ -15,7 +15,12 @@ const props = defineProps({
isDraggable: { isDraggable: {
type: Boolean, type: Boolean,
required: false required: false
} },
showType: {
type: String as PropType<'spot' | null>,
required: false,
validator: (value: string) => ['spot'].includes(value),
},
}); });
@ -49,6 +54,7 @@ const edit = (item) => {
item = baseSmartBlock(); item = baseSmartBlock();
} }
smartBlockStore.loadSmartBlock(item); smartBlockStore.loadSmartBlock(item);
smartBlockStore.currentSmartBlock.smart_block_type = props.showType;
itemEdited.value = item; itemEdited.value = item;
console.log(smartBlockStore) console.log(smartBlockStore)
} }

View file

@ -1,11 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import {ref, onMounted, type PropType} from "vue";; import {ref, onMounted, type PropType} from "vue";
;
import ShowScheduleForm from "@partials/show/ShowScheduleForm.vue"; import ShowScheduleForm from "@partials/show/ShowScheduleForm.vue";
import {useShowStore} from "@/stores/show.store.ts"; import {useShowStore} from "@stores/show/show.store.ts";
import {getUser} from "@models/User.ts"; import {getUser} from "@models/User.ts";
import {getPlaylist} from "@models/playlist.ts"; import {getPlaylist} from "@models/playlist.ts";
import ColorPickerButton from "@partials/fields/misc/ColorPickerButton.vue"; import ColorPickerButton from "@partials/fields/misc/ColorPickerButton.vue";
import {useShowDaysStore} from "@/stores/showDays.store.ts"; import {useShowDaysStore} from "@stores/show/showDays.store.ts";
// Props and emits // Props and emits
const props = defineProps({ const props = defineProps({
@ -13,6 +15,11 @@ const props = defineProps({
type: Number as PropType<number | null>, type: Number as PropType<number | null>,
required: true, required: true,
}, },
showType: {
type: String as PropType<'show' | 'spot'>,
required: true,
validator: (value: string) => ['show', 'spot'].includes(value),
},
}); });
const emits = defineEmits(['go-back']); const emits = defineEmits(['go-back']);
@ -31,15 +38,27 @@ const showDaysStore = useShowDaysStore()
// Funcs // Funcs
onMounted(async () => { onMounted(async () => {
loading.value = true loading.value = true
if (props.showId === null ) { // Prepare show store
if (props.showId === null) {
showStore.resetShow() showStore.resetShow()
showStore.currentShow.showType = props.showType
} else { } else {
const selectedShow = await showStore.getShow(props.showId, {withDjs: true}) const withDjs = props.showType === 'show';
const selectedShow = await showStore.getShow(props.showId, {showType: props.showType, withDjs: withDjs})
showStore.loadShow(selectedShow) showStore.loadShow(selectedShow)
showStore.currentShow.showType = props.showType
} }
usersDJs.value = await getUser({role: 'dj'});
playlists.value = await getPlaylist({}); // fill store
loading.value = false let playlistOptions = {playlistType: 'spot'};
if (props.showType === 'show') {
usersDJs.value = await getUser({role: 'dj'});
playlistOptions.playlistType = null;
}
playlists.value = await getPlaylist(playlistOptions);
loading.value = false;
}) })
const toggleShowScheduleForm = () => { const toggleShowScheduleForm = () => {
@ -105,7 +124,7 @@ const createShow = () => {
</v-col> </v-col>
<!-- URL Field --> <!-- URL Field -->
<v-col cols="12" md="6" lg="4"> <v-col v-if="props.showType == 'show'" cols="12" md="6" lg="4">
<v-card> <v-card>
<v-text-field <v-text-field
v-model="showStore.currentShow.url" v-model="showStore.currentShow.url"
@ -197,7 +216,7 @@ const createShow = () => {
<!-- TODO Instead of the dj name, obj obj is shown --> <!-- TODO Instead of the dj name, obj obj is shown -->
<!-- DJs Select --> <!-- DJs Select -->
<v-col cols="12" md="6" lg="4"> <v-col v-if="props.showType == 'show'" cols="12" md="6" lg="4">
<v-card> <v-card>
<v-select <v-select
v-model="showStore.currentShow.showDjs" v-model="showStore.currentShow.showDjs"
@ -216,8 +235,11 @@ const createShow = () => {
<v-card-actions> <v-card-actions>
<v-btn color="accent" @click="goBack">Torna indietro</v-btn> <v-btn color="accent" @click="goBack">Torna indietro</v-btn>
<v-btn v-if="showStore.currentShow.id" color="accent" @click="showStore.updateShow()" :disabled="!isFormValid" >Salva</v-btn> <v-btn v-if="showStore.currentShow.id" color="accent" @click="showStore.updateShow()"
<v-btn color="accent" @click="toggleShowScheduleForm" :disabled="!isFormValid" >Regole di programmazione</v-btn> :disabled="!isFormValid">Salva
</v-btn>
<v-btn color="accent" @click="toggleShowScheduleForm" :disabled="!isFormValid">Regole di programmazione
</v-btn>
</v-card-actions> </v-card-actions>
</v-form> </v-form>
</template> </template>

View file

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import ShowStartEndTime from "@partials/fields/show/ShowStartEndTime.vue"; import ShowStartEndTime from "@partials/fields/show/ShowStartEndTime.vue";
import {useShowInstanceStore} from "@/stores/showInstance.store.ts"; import {useShowInstanceStore} from "@stores/show/showInstance.store.ts";
import {onMounted, ref, watch, type PropType} from "vue"; import {onMounted, ref, watch, type PropType} from "vue";
import {baseShowInstance, type ShowInstance} from "@models/show/showInstance.ts"; import {baseShowInstance, type ShowInstance} from "@models/show/showInstance.ts";
import {extractTime} from "@/helpers/DateFormatter.ts"; import {extractTime} from "@/helpers/DateFormatter.ts";
@ -8,7 +8,7 @@ import {getPlaylistContent} from "@models/playlist.ts";
import Sources from "@partials/Sources.vue"; import Sources from "@partials/Sources.vue";
import TrackList from "@partials/TrackList.vue"; import TrackList from "@partials/TrackList.vue";
import {DateTime} from "luxon"; import {DateTime} from "luxon";
import {useShowStore} from "@/stores/show.store.ts"; import {useShowStore} from "@stores/show/show.store.ts";
const emits = defineEmits(['toggle-menu-edit-instance']); const emits = defineEmits(['toggle-menu-edit-instance']);
// Props // Props

View file

@ -3,7 +3,7 @@ import {onMounted, type PropType, ref, watch} from 'vue';
import {showRepetitionData} from "@models/show/ShowRepetition.ts"; import {showRepetitionData} from "@models/show/ShowRepetition.ts";
import DaysCheckbox from "@partials/fields/show/DaysCheckbox.vue"; import DaysCheckbox from "@partials/fields/show/DaysCheckbox.vue";
import ShowStartEndTime from "@partials/fields/show/ShowStartEndTime.vue"; import ShowStartEndTime from "@partials/fields/show/ShowStartEndTime.vue";
import {useShowDaysStore} from "@/stores/showDays.store.ts"; import {useShowDaysStore} from "@stores/show/showDays.store.ts";
// Emits and props // Emits and props
const emits = defineEmits(['toggle-show-schedule-form', 'trigger-show-creation']) const emits = defineEmits(['toggle-show-schedule-form', 'trigger-show-creation'])

View file

@ -40,6 +40,7 @@ export function playlist(item) {
} }
// TODO playlist interface // TODO playlist interface
// TODO Add filter if playlist is spot
export const getPlaylist = async (options: { export const getPlaylist = async (options: {
id?: number | null; id?: number | null;
scheduled?: number | null; scheduled?: number | null;

View file

@ -11,6 +11,7 @@ export interface Playlist {
description?: string; // Opzionale description?: string; // Opzionale
length: number; // Durata della playlist length: number; // Durata della playlist
contents: PlaylistContent[]; contents: PlaylistContent[];
playlist_type: 'spot' | null
} }
export const basePlaylist = (): Playlist => { export const basePlaylist = (): Playlist => {
@ -21,6 +22,7 @@ export const basePlaylist = (): Playlist => {
description: '', description: '',
length: 0, length: 0,
contents: [], contents: [],
playlist_type: null
} }
} }

View file

@ -27,6 +27,9 @@ export interface Show {
showDjs?: ShowDJs[]; showDjs?: ShowDJs[];
showInstances?: ShowInstance[]; showInstances?: ShowInstance[];
playlist?: any; playlist?: any;
// Extra
showType: 'show' | "spot" // Either show or spot
} }
export const baseShow = (): Show => { export const baseShow = (): Show => {
@ -46,6 +49,7 @@ export const baseShow = (): Show => {
autoplaylistRepeat: false, autoplaylistRepeat: false,
showDjs: null, showDjs: null,
showDays: null, showDays: null,
showType: "show"
} }
} }

View file

@ -17,6 +17,7 @@ export interface SmartBlock {
contents?: SmartBlockContent[]; // Contenuti associati (opzionale) contents?: SmartBlockContent[]; // Contenuti associati (opzionale)
criteria?: SmartBlockCriteria[]; // Criteri associati (opzionale) criteria?: SmartBlockCriteria[]; // Criteri associati (opzionale)
tracks?: SmartBlockContent[]; tracks?: SmartBlockContent[];
smart_block_type: 'spot' | null;
} }
export const baseSmartBlock = (): SmartBlock => { export const baseSmartBlock = (): SmartBlock => {
@ -30,6 +31,7 @@ export const baseSmartBlock = (): SmartBlock => {
contents: null, contents: null,
criteria: [], criteria: [],
tracks: [], tracks: [],
smart_block_type: null,
} }
} }

View file

@ -30,7 +30,7 @@ export function playlist_page() {
params: { params: {
page: page_info.page, page: page_info.page,
per_page: page_info.itemsPerPage, per_page: page_info.itemsPerPage,
all: search.value all: search.value,
} }
}).then((response) => { }).then((response) => {
console.log(response) console.log(response)

View file

@ -18,7 +18,12 @@ export function show_page() {
const headers = showTableHeader const headers = showTableHeader
const getItems = async (options) => { const getItems = async (options) => {
return getShows(options).then(showList => { const showSearchOptions = {
page: options.page,
per_page: options.itemsPerPage,
all: search.value,
}
return getShows(showSearchOptions).then(showList => {
listData.itemsPerPage = showList.per_page; listData.itemsPerPage = showList.per_page;
listData.first_page = showList.from; listData.first_page = showList.from;
listData.last_page = showList.last_page; listData.last_page = showList.last_page;

View file

@ -1,12 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import {computed, defineAsyncComponent} from 'vue'; import {computed, defineAsyncComponent, ref, watch} from 'vue';
// Props and data
const props = defineProps({ const props = defineProps({
page: Object, page: Object,
}); });
const currentPage = computed(() => props.page.id) const currentPage = computed(() => props.page.id)
const showType = ref<'show' | 'spot' | null>(null)
/** /**
* ToDo: * ToDo:
*/ */
@ -18,14 +21,31 @@ const tabs = {
blocks: defineAsyncComponent(() => import('@components/content/SmartBlock.vue')), blocks: defineAsyncComponent(() => import('@components/content/SmartBlock.vue')),
webstream: defineAsyncComponent(() => import('@components/content/Webstream.vue')), webstream: defineAsyncComponent(() => import('@components/content/Webstream.vue')),
podcast: defineAsyncComponent(() => import('@components/content/Podcast.vue')), podcast: defineAsyncComponent(() => import('@components/content/Podcast.vue')),
spot: defineAsyncComponent(() => import('@components/content/Show.vue')),
'spot-playlist': defineAsyncComponent(() => import('@components/content/Playlist.vue')),
'spot-blocks': defineAsyncComponent(() => import('@components/content/SmartBlock.vue')),
} }
watch(currentPage, (newVal) => {
showType.value = null
switch (true) {
case newVal === 'show':
showType.value = 'show'
break
case ['spot', 'spot-playlist', 'spot-block'].some(item => newVal.includes(item)):
showType.value = 'spot'
break
}
}, {immediate: true})
</script> </script>
<template> <template>
<v-col> <v-col>
<!-- <keep-alive>--> <!-- <keep-alive>-->
<Component :is="tabs[currentPage]" /> <Component :is="tabs[currentPage]"
<!-- </keep-alive>--> v-bind="showType ? { showType: showType } : {}"
/>
<!-- </keep-alive>-->
</v-col> </v-col>
</template> </template>

View file

@ -30,13 +30,27 @@ const pages = [
id: 'podcast', id: 'podcast',
name: 'Podcast', name: 'Podcast',
component: '@components/content/Podcast.vue', component: '@components/content/Podcast.vue',
component: '@components/content/Blocks.vue',
}, },
{ {
id: 'webstream', id: 'webstream',
name: 'Webstream', name: 'Webstream',
component: '@components/content/Webstream.vue', component: '@components/content/Webstream.vue',
} },
{
id: 'spot',
name: 'Spot',
component: '@components/content/Show.vue',
},
{
id: 'spot-playlist',
name: 'Spot playlist',
component: '@components/content/Playlist.vue',
},
{
id: 'spot-blocks',
name: 'Spot Blocchi dinamici',
component: '@components/content/SmartBlock.vue',
},
]; ];
</script> </script>

View file

@ -1,9 +1,9 @@
import {defineStore} from 'pinia' import {defineStore} from 'pinia'
import {type Show, baseShow} from '@models/show/show' import {type Show, baseShow} from '@models/show/show.ts'
import type {ShowDays} from "@models/show/showDays"; import type {ShowDays} from "@models/show/showDays.ts";
import axios, {type AxiosResponse} from "axios"; import axios, {type AxiosResponse} from "axios";
import {camelToSnake, cleanOptions, snakeToCamel} from "@/helpers/AxiosHelper.ts"; import {camelToSnake, cleanOptions, snakeToCamel} from "@/helpers/AxiosHelper.ts";
import { extractTimeUTC} from "@/helpers/DateFormatter.ts"; import {extractTimeUTC} from "@/helpers/DateFormatter.ts";
import {DateTime} from "luxon"; import {DateTime} from "luxon";
export const useShowStore = defineStore('show', { export const useShowStore = defineStore('show', {
@ -19,19 +19,28 @@ export const useShowStore = defineStore('show', {
this.currentShow[payload.key] = payload.value this.currentShow[payload.key] = payload.value
}, },
resetShow() { resetShow() {
this.currentShow = { ...baseShow() }; this.currentShow = {...baseShow()};
}, },
async getShow(id: Number, options: { async getShow(
withDjs?: boolean | null; id: number,
isScheduled?: boolean | null; {
}) { showType = 'show',
const filteredParams = cleanOptions(options); withDjs,
isScheduled,
}: {
showType?: 'show' | 'spot';
withDjs?: boolean | null;
isScheduled?: boolean | null;
} = {}
) {
const filteredParams = cleanOptions({showType, withDjs, isScheduled});
return await axios.get(`/show/${id}`, {params: filteredParams}) return await axios.get(`/show/${id}`, {params: filteredParams})
.then((response: AxiosResponse) => { .then((response: AxiosResponse) => {
return snakeToCamel(response.data); return snakeToCamel(response.data);
}).catch((error: Error) => { }).catch((error: Error) => {
console.log("Error: " + error); console.log("Error: " + error);
}) });
}, },
async createShow() { async createShow() {
let showData = {...this.currentShow}; let showData = {...this.currentShow};

View file

@ -1,5 +1,5 @@
import {defineStore} from 'pinia' import {defineStore} from 'pinia'
import type {ShowDays} from "@models/show/showDays"; import type {ShowDays} from "@models/show/showDays.ts";
import axios, {type AxiosResponse} from "axios"; import axios, {type AxiosResponse} from "axios";
import {camelToSnake, cleanOptions, snakeToCamel} from "@/helpers/AxiosHelper.ts"; import {camelToSnake, cleanOptions, snakeToCamel} from "@/helpers/AxiosHelper.ts";
import {extractTime, extractTimeUTC} from "@/helpers/DateFormatter.ts"; import {extractTime, extractTimeUTC} from "@/helpers/DateFormatter.ts";