Merge branch 'dev' of ssh://git.congegni.net:4022/Congegni/sintonia_webapp into dev

This commit is contained in:
Marco Cavalli 2025-04-03 15:01:11 +02:00
commit 36d3deab44
26 changed files with 521 additions and 243 deletions

View file

@ -15,7 +15,14 @@ enum ShowRepeatType: int
return constant("self::$name"); return constant("self::$name");
} }
public static function fromValue(int $value){
return constant("self::$value"); public static function fromValue(string $value){
foreach (self::cases() as $case) {
if ($case->value === $value) {
return $case;
}
}
throw new \InvalidArgumentException("No matching UserType found for value: $value");
} }
} }

View file

@ -0,0 +1,37 @@
<?php
namespace App\Filters\Show;
use App\Filters\FiltersType\IsFilter;
use App\Filters\FiltersType\LikeFilter;
class ShowDaysFilters
{
protected $filters = [
'show_id' => IsFilter::class,
];
public function apply($query, $filters)
{
foreach ($this->receivedFilters($filters) as $name => $value) {
switch ($name) {
case 'all':
$name = array_diff(array_keys($this->filters), ['all']);
$filterInstance = new $this->filters['all']();
break;
default:
$filterInstance = new $this->filters[$name]();
break;
}
$query = $filterInstance($query, $name, $value);
}
return $query;
}
public function receivedFilters($filters)
{
return $filters->only(array_keys($this->filters));
}
}

View file

@ -40,6 +40,7 @@ class CarbonShowRepetition
while ($currentDate->lte($dateStopInterval)) { while ($currentDate->lte($dateStopInterval)) {
if ($repeatTypeEnum === ShowRepeatType::monthly) { if ($repeatTypeEnum === ShowRepeatType::monthly) {
$nextMonth = $currentDate->addMonth(); $nextMonth = $currentDate->addMonth();
// With a flag we can skip this check and repeating based on the given month number
if ($nextMonth->day > $currentDate->day) { if ($nextMonth->day > $currentDate->day) {
$nextMonth->day = $currentDate->day; $nextMonth->day = $currentDate->day;
} }
@ -52,4 +53,13 @@ class CarbonShowRepetition
return $dates; return $dates;
} }
public static function getNextStartDate(string $showFirstDate, int $showDayNumber): Carbon {
$date = Carbon::createFromFormat('Y-m-d', $showFirstDate);
$dayOfWeek = $date->dayOfWeek;
if($dayOfWeek != $showDayNumber){
$date = $date->next($showDayNumber);
}
return $date;
}
} }

View file

@ -42,16 +42,17 @@ class ShowController extends Controller
public function store(Request $request) public function store(Request $request)
{ {
try { try {
$showInfos = $request->show; $showData = $request->all();
$showDaysRules = $request->showDaysRules; $showDays = $showData['show_days'];
$showDjs = $request->showDjs; $showDJs = $showData['show_djs'];
$show = Show::firstOrCreate($showInfos); $this->createShow($showData, $showDays, $showDJs);
$this->manageShowDays($show, $showDaysRules); return response()->json([
$this->manageShowDjs($showDjs, $show); 'status' => 'success',
}catch(Exception $e){ 'message' => 'Show saved successfully!'
]);
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()], 500); return response()->json(['message' => $e->getMessage()], 500);
} }
return response()->json(['message' => 'Show created successfully']);
} }
public function show(ShowResource $show) public function show(ShowResource $show)

View file

@ -0,0 +1,49 @@
<?php
namespace App\Http\Controllers\Show;
use App\Filters\Show\ShowFilters;
use App\Http\Controllers\Controller;
use App\Http\Requests\ShowDaysRequest;
use App\Http\Resources\ShowDaysResource;
use App\Models\Show\Show;
use App\Models\Show\ShowDays;
use App\Traits\Show\ShowDaysTrait;
use Illuminate\Http\ReqshowDaysFlatest;
use Illuminate\Http\Request;
class ShowDaysController extends Controller
{
use ShowDaysTrait;
public function index(Request $request)
{
$queryParams = collect($request->except('flattenShowDays'));
$showDays = (new ShowDays())->searchFilter($queryParams)->get();
if($request->flattenShowDays) $showDays = $this->showDaysFlat($showDays);
return response()->json($showDays);
}
public function store(ShowDaysRequest $request)
{
return new ShowDaysResource(ShowDays::create($request->validated()));
}
public function show(ShowDays $ShowDays)
{
return new ShowDaysResource($ShowDays);
}
public function update(ShowDaysRequest $request, ShowDays $ShowDays)
{
$ShowDays->update($request->validated());
return new ShowDaysResource($ShowDays);
}
public function destroy(ShowDays $ShowDays)
{
$ShowDays->delete();
return response()->json();
}
}

View file

@ -2,6 +2,7 @@
namespace App\Models\Show; namespace App\Models\Show;
use App\Filters\Show\ShowDaysFilters;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@ -41,4 +42,9 @@ class ShowDays extends Model
{ {
return $this->belongsTo(Show::class, 'show_id'); return $this->belongsTo(Show::class, 'show_id');
} }
public function scopeSearchFilter($query, $request) {
$filters = new ShowDaysFilters();
return $filters->apply($query, $request);
}
} }

View file

@ -3,9 +3,12 @@
namespace App\Traits\Show; namespace App\Traits\Show;
use App\Enums\ShowRepeatType; use App\Enums\ShowRepeatType;
use App\Helpers\CarbonShowRepetition;
use App\Helpers\Preferences;
use App\Lib\RabbitMQSender; use App\Lib\RabbitMQSender;
use Illuminate\Support\Collection;
Trait ShowDaysTrait trait ShowDaysTrait
{ {
/** /**
* Each show may have a repetition rules, we set them in the db * Each show may have a repetition rules, we set them in the db
@ -13,42 +16,43 @@ Trait ShowDaysTrait
* @param $showDaysRules * @param $showDaysRules
* *
* @return void * @return void
* @throws \Exception
*/ */
public function createShowDays($show, $showDays) public function createShowDays($show, $showDays): void
{ {
for ($i = 0; $i < count($showDays['day']); $i++) { try {
$showDay = $showDays; // TODO this is incorrect, look up the preference helper, getDefaultTimezone is called statically but it should not
$showDay['repeat_type'] = ShowRepeatType::fromName($showDays['repeat_type']); $timezone = config('app.timezone');
$showDay['day'] = $showDays['day'][$i]; foreach ($showDays['day'] as $day) {
$showDay = $showDays;
// Check if showDay already exists for this day and show ID $showDay['first_show'] = CarbonShowRepetition::getNextStartDate($showDays['first_show'], $day);
$existingShowDay = $show->showDays()->where('day', $showDay['day'])->first(); $showDay['repeat_type'] = ShowRepeatType::fromName($showDays['repeat_type'])->value;
$showDay['day'] = $day;
if ($existingShowDay) { $show->showDays()->create(
$existingShowDay->update([ [
'first_show' => $showDay['first_show'], 'first_show' => $showDay['first_show'],
'start_time' => $showDay['start_time'], 'start_time' => $showDay['start_time'],
'timezone' => $showDay['timezone'], 'timezone' => $showDay['timezone'],
'duration' => $showDay['duration'], 'duration' => $showDay['duration'],
'repeat_type' => $showDay['repeat_type']->value, 'repeat_type' => $showDay['repeat_type'],
]); 'day' => $showDay['day'],
continue; 'timezone' => $timezone,
]
);
} }
$show->showDays()->create($showDay); } catch (\Exception $e) {
throw new \Exception($e->getMessage());
} }
} }
public function manageShowDays($show, $showDays) public function showDaysFlat(Collection $showDays): array {
{ $baseProps = $showDays->first()->toArray();
// TODO Determine if the show days rules have been changed $earliestDate = $showDays->min('first_show');
// By editing the createShowDays, returning the days on which the rules have been changed $daysArray = $showDays->pluck('day')->toArray();
// one could trigger the creation of only specific instances
// The hard part relies on finding out which show instances return array_merge($baseProps, [
// belong to which showDays rules, as they do not have any fk to showDays 'first_show' => $earliestDate,
// Probably its complexity is higher than its advantages 'day' => $daysArray,
// if it is, then proceed to edit the show days rules and generate the show instances ]);
$this->createShowDays($show, $showDays);
$this->manageShowInstances($show);
RabbitMQSender::SendMessageToPypo('update_schedule', []);
} }
} }

View file

@ -11,7 +11,7 @@ trait ShowDjTrait
/** /**
* @throws Exception * @throws Exception
*/ */
public function manageShowDjs(array $showDjs, Show $show) public function manageShowDjs(Show $show, array $showDjs)
{ {
// TODO Add protection so only Editor and Admin can edit the djs // TODO Add protection so only Editor and Admin can edit the djs
@ -22,21 +22,26 @@ trait ShowDjTrait
} }
} }
// Fetch current DJs for the show try {
$currentDjs = $show->showDjs()->pluck('subjs_id')->toArray(); // Fetch current DJs for the show
$currentDjs = $show->showDjs()->pluck('subjs_id')->toArray();
// Compute the difference // Compute the difference
$djsToAdd = array_diff($showDjs, $currentDjs); $djsToAdd = array_diff($showDjs, $currentDjs);
$djsToRemove = array_diff($currentDjs, $showDjs); $djsToRemove = array_diff($currentDjs, $showDjs);
// Remove DJs not in the list // Remove DJs not in the list
foreach ($djsToRemove as $djId) { foreach ($djsToRemove as $djId) {
$show->showDjs()->where('subjs_id', $djId)->delete(); $show->showDjs()->where('subjs_id', $djId)->delete();
}
// Add new DJs
foreach ($djsToAdd as $djId) {
$show->showDjs()->make(['subjs_id' => $djId]);
}
} catch (\Exception $e) {
return $e->getMessage();
} }
// Add new DJs
foreach ($djsToAdd as $djId) {
$show->showDjs()->updateOrCreate(['subjs_id' => $djId]);
}
} }
} }

View file

@ -6,6 +6,7 @@ use App\Helpers\CarbonShowRepetition;
use App\Helpers\LengthFormatter; use App\Helpers\LengthFormatter;
use App\Http\Resources\ShowResource; use App\Http\Resources\ShowResource;
use App\Models\Preference; use App\Models\Preference;
use App\Models\Show\Show;
use App\Models\ShowInstances\ShowInstances; use App\Models\ShowInstances\ShowInstances;
use Carbon\Carbon; use Carbon\Carbon;
use Carbon\CarbonInterval; use Carbon\CarbonInterval;
@ -18,33 +19,31 @@ trait ShowInstancesTrait
*/ */
public function checkOverlappingInstances(array $instancesDates, $duration, int $showId): void public function checkOverlappingInstances(array $instancesDates, $duration, int $showId): void
{ {
$existingRanges = ShowInstances::where('show_id', '!=', $showId)
->get(['starts', 'ends'])
->map(fn($i) => [$i->starts, $i->ends]);
foreach ($instancesDates as $date) { foreach ($instancesDates as $date) {
$instanceStartDate = $date; $newStart = $date;
$instanceEndDate = clone $date; $newEnd = (clone $date)->addHours($duration);
$instanceEndDate->addHours($duration);
// Query to check for overlaps $hasOverlap = $existingRanges->contains(
$overlapExists = ShowInstances::where( fn($range) => $newStart < $range[1] && $newEnd > $range[0]
function ($query) use ($instanceStartDate, $instanceEndDate, $showId) { );
$query->where('starts', '<=', $instanceEndDate)
->where('ends', '>=', $instanceStartDate)
->where('show_id','!=', $showId);
}
)->exists();
if ($overlapExists) { if ($hasOverlap) {
throw new Exception('Overlapping shows'); // Overlap found throw new Exception("Overlap detected between $newStart and $newEnd");
} }
} }
} }
public function createShowInstances($instancesDates, $showDay, $show) public function createShowInstances($instancesDates, $showDayDuration, $showId)
{ {
$showInstances = []; $showInstances = [];
foreach ($instancesDates as $instanceDate) { foreach ($instancesDates as $instanceDate) {
$showStartDate = $instanceDate->copy(); $showStartDate = $instanceDate->copy();
$duration = CarbonInterval::createFromFormat('H:i:s', $showDay->duration); $duration = CarbonInterval::createFromFormat('H:i', $showDayDuration);
$showEndDate = $showStartDate->copy()->add($duration); $showEndDate = $showStartDate->copy()->add($duration);
$timeFilled = LengthFormatter::generateStringTimeFilled($showStartDate, $showEndDate); $timeFilled = LengthFormatter::generateStringTimeFilled($showStartDate, $showEndDate);
@ -53,7 +52,7 @@ trait ShowInstancesTrait
$showInstances[] = [ $showInstances[] = [
'starts' => $showStartDate, 'starts' => $showStartDate,
'ends' => $showEndDate, 'ends' => $showEndDate,
'show_id' => $show->id, 'show_id' => $showId,
'record' => 0, 'record' => 0,
'rebroadcast' => 0, 'rebroadcast' => 0,
'time_filled' => $timeFilled, 'time_filled' => $timeFilled,
@ -73,34 +72,36 @@ trait ShowInstancesTrait
* @param ShowResource $ShowResource * @param ShowResource $ShowResource
* *
* @return void * @return void
* @throws \Exception * @throws Exception
*/ */
public function manageShowInstances($show) public function manageShowInstances(Show $show)
{ {
$showDays = $show->showDays; try {
$generationLimitDate = Preference::where('keystr', 'shows_populated_until')->value('valstr'); $generationLimitDate = Preference::where('keystr', 'shows_populated_until')->value('valstr');
$generationLimitDate = Carbon::createFromFormat('Y-m-d H:i:s', $generationLimitDate); $generationLimitDate = Carbon::createFromFormat('Y-m-d H:i:s', $generationLimitDate);
$showInstances = []; $showInstances = [];
foreach ($show->showDays as $showDay) {
try {
$lastShowDate = $showDay->last_show ?? $generationLimitDate;
$instancesDates = CarbonShowRepetition::generateDates(
$showDay->repeat_type,
$showDay->first_show,
$showDay->start_time,
$lastShowDate
);
foreach ($showDays as $showDay) { $this->checkOverlappingInstances($instancesDates, $showDay->duration, $show->id);
try {
$lastShowDate = $showDay->last_show ?? $generationLimitDate;
$instancesDates = CarbonShowRepetition::generateDates(
$showDay->repeat_type,
$showDay->first_show,
$showDay->start_time,
$lastShowDate
);
$this->checkOverlappingInstances($instancesDates, $showDay->duration, $show->id); $showInstances[] = $this->createShowInstances($instancesDates, $showDay->duration, $show->id);
} catch (Exception $e) {
$showInstances[] = $this->createShowInstances($instancesDates, $showDay, $show); throw new Exception($e->getMessage(), 500);
} catch (\Exception $e) { }
throw new \Exception($e->getMessage(), 500);
} }
} foreach ($showInstances as $showInstance) {
foreach ($showInstances as $showInstance) { $show->showInstances()->createMany($showInstance);
$show->showInstances()->createMany($showInstance); }
} catch (Exception $e) {
throw new Exception($e->getMessage(), 500);
} }
} }
} }

View file

@ -2,7 +2,39 @@
namespace App\Traits\Show; namespace App\Traits\Show;
use App\Lib\RabbitMQSender;
use App\Models\Show\Show;
use App\Models\Show\ShowHosts;
use Illuminate\Support\Facades\DB;
trait ShowTrait trait ShowTrait
{ {
/**
* @param array $showData
* @param ShowDays[] $showDays
* @param ShowHosts[] $showDJs
*
* @return string|void
* @throws \Exception
*/
public function createShow(array $showData, array $showDays, array|null $showDJs)
{
try {
DB::beginTransaction();
$show = Show::create($showData);
$this->createShowDays($show, $showDays);
if (isset($showDJs)) {
$this->manageShowDjs($show, $showDJs);
}
$this->manageShowInstances($show);
$show->save();
DB::commit();
RabbitMQSender::SendMessageToPypo('update_schedule', []);
} catch (\Exception $e) {
DB::rollBack();
throw new \Exception($e->getMessage());
} catch (\Throwable $e) {
throw new \Exception($e->getMessage());
}
}
} }

View file

@ -1,20 +1,21 @@
<script setup lang="ts"> <script setup lang="ts">
import CalendarShowEvent from "@/components/content/partials/CalendarShowEvent.vue";
import {computed, onMounted, type Ref, ref} from "vue"; import {computed, onMounted, type Ref, ref} from "vue";
import {useAuthStore} from "@/stores/auth.store.ts"; import {useAuthStore} from "@/stores/auth.store.ts";
import ShowCreateEditForm from "@partials/show/ShowCreateEditForm.vue";
import {baseShow, deleteShow} from "@models/show/show.ts"; import {baseShow, deleteShow} from "@models/show/show.ts";
import type {calendarShowEvent} from "@models/misc/calendarShowEvent.ts";
import { nextTick } from "vue";
import {baseShowInstance, getShowInstances, type ShowInstance} from "@models/show/showInstance.ts"; import {baseShowInstance, getShowInstances, type ShowInstance} from "@models/show/showInstance.ts";
import {setShowInstancesForCalendar} from "@/composables/content/dashboard_page.ts"; import {setShowInstancesForCalendar} from "@/composables/content/dashboard_page.ts";
import {baseShowDays, getShowDays} from "@models/show/showDays.ts"; import {baseShowDays, getShowDays} from "@models/show/showDays.ts";
import type {calendarShowEvent} from "@models/misc/calendarShowEvent.ts";
import ShowInstanceForm from "@partials/show/ShowInstanceForm.vue"; import ShowInstanceForm from "@partials/show/ShowInstanceForm.vue";
import ShowForm from "@partials/show/ShowForm.vue";
import CalendarShowEvent from "@partials/show/CalendarShowEvent.vue"
import {extractTime} from "@/helpers/DateFormatter.ts";
// Store
const auth = useAuthStore(); const auth = useAuthStore();
const userRole = auth.userData.user.role; const userRole = auth.userData.user.role;
// Data
const editMode = ref(false); const editMode = ref(false);
const showCreateEditMode = ref(false); const showCreateEditMode = ref(false);
const showInstanceCreateEditMode = ref(false); const showInstanceCreateEditMode = ref(false);
@ -22,6 +23,9 @@ const showInstances = ref<ShowInstance[]>([]);
let showSelected = ref(baseShow()); let showSelected = ref(baseShow());
let selectedShowInstance = ref(baseShowInstance()); let selectedShowInstance = ref(baseShowInstance());
const shows: Ref<calendarShowEvent[]> = computed(() => setShowInstancesForCalendar(showInstances.value));
// Funcs
onMounted(async () => { onMounted(async () => {
const today = new Date(); const today = new Date();
const startOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 1); const startOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 1);
@ -33,9 +37,6 @@ onMounted(async () => {
showInstances.value = await getShowInstances(options); showInstances.value = await getShowInstances(options);
}); });
const shows: Ref<calendarShowEvent[]> = computed(() => setShowInstancesForCalendar(showInstances.value));
const toggleEditMode = () => { const toggleEditMode = () => {
editMode.value = !editMode.value; editMode.value = !editMode.value;
}; };
@ -51,7 +52,11 @@ const contextMenuEditInstance = (showInstanceId: number) => {
const contextMenuEditShow = async (showInstanceId: number) => { const contextMenuEditShow = async (showInstanceId: number) => {
showSelected.value = showInstances.value[showInstanceId].show; showSelected.value = showInstances.value[showInstanceId].show;
showSelected.value.showDays = await getShowDays({showId: showSelected.value.id, flattenShowDays: true}); let showDaysRules = await getShowDays({show_id: showSelected.value.id, flattenShowDays: true});
showDaysRules.firstShow = new Date(showDaysRules.firstShow);
// TODO Timezone problems
showDaysRules.startTime = extractTime(showDaysRules.startTime, true);
showSelected.value.showDays = showDaysRules;
showCreateEditMode.value = true; showCreateEditMode.value = true;
}; };
@ -79,7 +84,7 @@ const resetItemEdited = () => {
<template> <template>
<template v-if="showCreateEditMode || showInstanceCreateEditMode"> <template v-if="showCreateEditMode || showInstanceCreateEditMode">
<ShowCreateEditForm v-if="showCreateEditMode" :show="showSelected" @go-back="resetItemEdited" /> <ShowForm v-if="showCreateEditMode" :show="showSelected" @go-back="resetItemEdited" />
<ShowInstanceForm v-if="showInstanceCreateEditMode" :showInstance="selectedShowInstance" @toggle-menu-edit-instance="toggleMenuEditInstance" /> <ShowInstanceForm v-if="showInstanceCreateEditMode" :showInstance="selectedShowInstance" @toggle-menu-edit-instance="toggleMenuEditInstance" />
</template> </template>
<template v-else> <template v-else>

View file

@ -3,7 +3,7 @@ import {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";
import ShowCreateEditForm from "@partials/show/ShowCreateEditForm.vue"; import ShowForm from "@partials/show/ShowForm.vue";
import {baseShow, type Show} from "@models/show/show"; 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()
@ -82,7 +82,7 @@ watch(search, (newValue, oldValue) => {
</script> </script>
<template> <template>
<ShowCreateEditForm <ShowForm
v-if="showSelected.id !== null && !dialog.open" v-if="showSelected.id !== null && !dialog.open"
:show="showSelected" :show="showSelected"
@go-back="resetItemEdited" @go-back="resetItemEdited"

View file

@ -1,8 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import {WeekDaysData} from "@models/show/ShowRepetition.ts"; import {WeekDaysData} from "@models/show/ShowRepetition.ts";
import type {PropType} from "vue";
import type {Show} from "@models/show/show.ts";
const selectedDays = defineModel() const selectedDays = defineModel()
const props = defineProps({
fixedDay: {
type: Number,
required: false,
},
});
</script> </script>
<template> <template>
@ -14,6 +22,7 @@ const selectedDays = defineModel()
v-model="selectedDays" v-model="selectedDays"
:label="day.dayName" :label="day.dayName"
:value="day.type" :value="day.type"
:disabled="day.type === props.fixedDay"
/> />
</v-col> </v-col>
</v-row> </v-row>

View file

@ -2,7 +2,8 @@
import {onMounted, type PropType} from "vue"; import {onMounted, type PropType} from "vue";
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import {getShowInstances} from "@models/show/showInstance.ts"; import {getShowInstances} from "@models/show/showInstance.ts";
import {getHoursMinutesFromString, getTimeDiff} from "@/helpers/DateFormatter.ts";
// Emits, props and models
const emits = defineEmits(['update:duration']) const emits = defineEmits(['update:duration'])
const props = defineProps({ const props = defineProps({
@ -25,37 +26,26 @@ const props = defineProps({
}) })
const startTime = defineModel({default: '08:00'}); const startTime = defineModel({default: '08:00'});
// Data
const duration = ref('0:0'); const duration = ref('0:0');
const endTime = ref(''); const endTime = ref('');
const startMenu = ref(false); const startMenu = ref(false);
const endMenu = ref(false); const endMenu = ref(false);
const getDuration = (startTime: Date, endTime: Date) => { // Func
const diffInMilliseconds = Math.abs(startTime.getTime() - endTime.getTime());
const remainingHours = Math.floor((diffInMilliseconds % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const remainingMinutes = Math.floor((diffInMilliseconds % (1000 * 60 * 60)) / (1000 * 60));
return `${remainingHours}:${remainingMinutes}`;
};
const getHoursMinutes = (timeString: string) => {
const [ h, m ] = timeString.split(":");
const ms = new Date().setHours(h,m);
return new Date(ms)
};
const checkTime = () => { const checkTime = () => {
if (startTime.value && endTime) { if (startTime.value && endTime) {
const start = getHoursMinutes(startTime.value); const start: Date = getHoursMinutesFromString(startTime.value);
let end = getHoursMinutes(endTime.value); let end: Date = getHoursMinutesFromString(endTime.value);
if (end.getTime() <= start.getTime()) { if (end.getTime() <= start.getTime()) {
end = new Date(start); end = new Date(start);
end.setMinutes(start.getMinutes() + 60); end.setMinutes(start.getMinutes() + 60);
endTime.value = end.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit' });; endTime.value = end.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit' });;
} }
duration.value = getDuration(start, end); duration.value = getTimeDiff(start, end);
emits('update:duration', duration.value); emits('update:duration', duration.value);
} }
} }

View file

@ -3,7 +3,7 @@ import type { PropType} from "vue";
import type {ContextMenuType} from "@models/misc/contextMenu" import type {ContextMenuType} from "@models/misc/contextMenu"
import type {calendarShowEvent, ShowEventActionTrigger} from "@models/misc/calendarShowEvent.ts"; import type {calendarShowEvent, ShowEventActionTrigger} from "@models/misc/calendarShowEvent.ts";
import {calendarShowEventMenu} from "@models/misc/calendarShowEvent"; import {calendarShowEventMenu} from "@models/misc/calendarShowEvent";
import {ref, computed, onMounted} from 'vue'; import { ref } from 'vue';
import ContextMenu from '@partials/ContextMenu.vue'; import ContextMenu from '@partials/ContextMenu.vue';
const emit = defineEmits([ const emit = defineEmits([

View file

@ -7,6 +7,7 @@ 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";
// Props
const props = defineProps({ const props = defineProps({
show: { show: {
type: Object as PropType<Show>, type: Object as PropType<Show>,
@ -14,18 +15,22 @@ const props = defineProps({
}, },
}); });
// Data
let usersDJs = ref([]) let usersDJs = ref([])
let playlists = ref([]) let playlists = ref([])
const isLoading = ref(false); const loading = ref(false);
const showScheduleFormMode = ref(false); const showScheduleFormMode = ref(false);
const isFormValid = ref(false);
// Store
const showStore = useShowStore()
showStore.loadShow(props.show)
const showFormStore = useShowStore() // Funcs
onMounted(async () => { onMounted(async () => {
isLoading.value = true loading.value = true
await showFormStore.loadShow(props.show)
usersDJs.value = await getUser({role: 'dj'}); usersDJs.value = await getUser({role: 'dj'});
playlists.value = await getPlaylist({}); playlists.value = await getPlaylist({});
isLoading.value = false loading.value = false
}) })
const toggleScheduleForm = () => { const toggleScheduleForm = () => {
@ -34,32 +39,45 @@ const toggleScheduleForm = () => {
</script> </script>
<template> <template>
<v-card> <v-card
:disabled="loading"
:loading="loading"
>
<template v-slot:loader="{ isActive }">
<v-progress-linear
:active="isActive"
color="deep-purple"
height="4"
indeterminate
></v-progress-linear>
</template>
<v-card-title> <v-card-title>
<h3>Trasmissione</h3> <h3>Trasmissione</h3>
</v-card-title> </v-card-title>
<template v-if="showScheduleFormMode"> <template v-if="showScheduleFormMode">
<ShowScheduleForm <ShowScheduleForm
v-model="showFormStore.currentShow.showDays" v-model="showStore.currentShow.showDays"
@toggle-show-schedule-form="toggleScheduleForm" @toggle-show-schedule-form="toggleScheduleForm"
@save-show-schedule="showFormStore.saveSchedule" @trigger-show-creation="showStore.createShow"
/> />
</template> </template>
<template v-else> <template v-else>
<v-form> <v-form ref="form" v-model="isFormValid">
<v-card-text> <v-card-text>
<v-row no-gutters> <v-row no-gutters>
<!-- Name Field --> <!-- Name Field -->
<v-col cols="12" md="6" lg="4"> <v-col cols="12" md="6" lg="4">
<v-card> <v-card>
<v-text-field <v-text-field
v-model="showFormStore.currentShow.name" v-model="showStore.currentShow.name"
label="Nome" label="Nome"
density="compact" density="compact"
:required="true" @update:modelValue="value => showStore.updateField({ key: 'name', value })"
@update:modelValue="value => showFormStore.updateField({ key: 'name', value })" :rules="[v => !!v || 'Nome è obbligatorio']"
required="true"
/> />
</v-card> </v-card>
</v-col> </v-col>
@ -68,11 +86,11 @@ const toggleScheduleForm = () => {
<v-col cols="12" md="6" lg="4"> <v-col cols="12" md="6" lg="4">
<v-card> <v-card>
<v-text-field <v-text-field
v-model="showFormStore.currentShow.url" v-model="showStore.currentShow.url"
label="URL" label="URL"
density="compact" density="compact"
:required="true" :required="true"
@update:modelValue="value => showFormStore.updateField({ key: 'url', value })" @update:modelValue="value => showStore.updateField({ key: 'url', value })"
/> />
</v-card> </v-card>
</v-col> </v-col>
@ -81,11 +99,11 @@ const toggleScheduleForm = () => {
<v-col cols="12" md="6" lg="4"> <v-col cols="12" md="6" lg="4">
<v-card> <v-card>
<v-text-field <v-text-field
v-model="showFormStore.currentShow.genre" v-model="showStore.currentShow.genre"
label="Genere" label="Genere"
density="compact" density="compact"
:required="true" :required="true"
@update:modelValue="value => showFormStore.updateField({ key: 'genre', value })" @update:modelValue="value => showStore.updateField({ key: 'genre', value })"
/> />
</v-card> </v-card>
</v-col> </v-col>
@ -94,11 +112,11 @@ const toggleScheduleForm = () => {
<v-col cols="12" md="6" lg="4"> <v-col cols="12" md="6" lg="4">
<v-card> <v-card>
<v-textarea <v-textarea
v-model="showFormStore.currentShow.description" v-model="showStore.currentShow.description"
label="Descrizione" label="Descrizione"
density="compact" density="compact"
rows="2" rows="2"
@update:modelValue="value => showFormStore.updateField({ key: 'description', value })" @update:modelValue="value => showStore.updateField({ key: 'description', value })"
/> />
</v-card> </v-card>
</v-col> </v-col>
@ -107,9 +125,9 @@ const toggleScheduleForm = () => {
<v-col cols="12" md="6" lg="4"> <v-col cols="12" md="6" lg="4">
<v-card> <v-card>
<ColorPickerButton <ColorPickerButton
v-model="showFormStore.currentShow.backgroundColor" v-model="showStore.currentShow.backgroundColor"
label="Colore di sfondo" label="Colore di sfondo"
@update:modelValue="value => showFormStore.updateField({ key: 'backgroundColor', value })" @update:modelValue="value => showStore.updateField({ key: 'backgroundColor', value })"
/> />
</v-card> </v-card>
</v-col> </v-col>
@ -118,11 +136,11 @@ const toggleScheduleForm = () => {
<v-col cols="12" md="6" lg="4"> <v-col cols="12" md="6" lg="4">
<v-card> <v-card>
<v-file-input <v-file-input
v-model="showFormStore.currentShow.imagePath" v-model="showStore.currentShow.imagePath"
label="Percorso immagine" label="Percorso immagine"
density="compact" density="compact"
type="file" type="file"
@update:modelValue="value => showFormStore.updateField({ key: 'imagePath', value })" @update:modelValue="value => showStore.updateField({ key: 'imagePath', value })"
/> />
</v-card> </v-card>
</v-col> </v-col>
@ -133,24 +151,24 @@ const toggleScheduleForm = () => {
<v-card> <v-card>
<v-checkbox <v-checkbox
label="Attivare regole di ripetizione?" label="Attivare regole di ripetizione?"
v-model="showFormStore.currentShow.hasAutoplaylist" v-model="showStore.currentShow.hasAutoplaylist"
></v-checkbox> ></v-checkbox>
<v-checkbox <v-checkbox
v-model="showFormStore.currentShow.autoplaylistRepeat" v-model="showStore.currentShow.autoplaylistRepeat"
label="Ripetere playlist?" label="Ripetere playlist?"
:disabled="!showFormStore.currentShow.hasAutoplaylist" :disabled="!showStore.currentShow.hasAutoplaylist"
@update:modelValue="value => showFormStore.updateField({ key: 'autoplaylistRepeat', value })" @update:modelValue="value => showStore.updateField({ key: 'autoplaylistRepeat', value })"
/> />
<v-select <v-select
v-model="showFormStore.currentShow.autoplaylist_id" v-model="showStore.currentShow.autoplaylistId"
:items="playlists" :items="playlists"
label="Playlist" label="Playlist"
density="compact" density="compact"
item-title="name" item-title="name"
item-value="id" item-value="id"
:disabled="!showFormStore.currentShow.hasAutoplaylist" :disabled="!showStore.currentShow.hasAutoplaylist"
@update:modelValue="value => showFormStore.updateField({ key: 'autoplaylistId', value })" @update:modelValue="value => showStore.updateField({ key: 'autoplaylistId', value })"
/> />
</v-card> </v-card>
</v-card> </v-card>
@ -160,14 +178,14 @@ const toggleScheduleForm = () => {
<v-col cols="12" md="6" lg="4"> <v-col cols="12" md="6" lg="4">
<v-card> <v-card>
<v-select <v-select
v-model="showFormStore.currentShow.showDjs" v-model="showStore.currentShow.showDjs"
:items="usersDJs" :items="usersDJs"
label="DJs" label="DJs"
density="compact" density="compact"
item-title="login" item-title="login"
item-value="id" item-value="id"
multiple multiple
@update:modelValue="value => showFormStore.updateField({ key: 'showDjs', value })" @update:modelValue="value => showStore.updateField({ key: 'showDjs', value })"
/> />
</v-card> </v-card>
</v-col> </v-col>
@ -176,8 +194,8 @@ const toggleScheduleForm = () => {
<v-card-actions> <v-card-actions>
<v-btn color="accent" @click="$emit('goBack')">Torna indietro</v-btn> <v-btn color="accent" @click="$emit('goBack')">Torna indietro</v-btn>
<v-btn color="accent" @click="showFormStore.saveShow">Salva</v-btn> <v-btn v-if="showStore.currentShow.id" color="accent" @click="showStore.updateShow()" :disabled="!isFormValid" >Salva</v-btn>
<v-btn color="accent" @click="toggleScheduleForm">Salva e schedula</v-btn> <v-btn color="accent" @click="toggleScheduleForm" :disabled="!isFormValid" >Regole di programmazione</v-btn>
</v-card-actions> </v-card-actions>
</v-form> </v-form>
</template> </template>

View file

@ -1,39 +1,65 @@
<script setup lang="ts"> <script setup lang="ts">
import {onMounted, ref, watch} from 'vue'; import {onMounted, ref, watch} from 'vue';
import {useShowStore} from "@/stores/show.store.ts"; import {useShowStore} from "@/stores/show.store.ts";
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";
const emits =defineEmits(['toggle-show-schedule-form']) // Emits and props
const emits = defineEmits(['toggle-show-schedule-form', 'trigger-show-creation'])
const showFormStore = useShowStore(); // Store
const showStore = useShowStore();
// Data
const checkBoxEnd = ref(false)
const checkBoxRepetition = ref(false)
const isFormValid = ref(false)
// Funcs
onMounted(() => {
const startDayShow = showStore.currentShow.showDays.firstShow.getDay();
let showDays = checkBoxRepetition.value ? showStore.currentShow.showDays.day : [];
showDays.push(startDayShow);
showStore.updateShowDaysField({ key: 'day', value: showDays });
});
const updateShowStartDay = (newStartDate) => {
const previousStartDayShow = showStore.currentShow.showDays.firstShow.getDay();
let showDays = showStore.currentShow.showDays.day.filter((item) => item !== previousStartDayShow);
showStore.updateShowDaysField({ key: 'firstShow', value: newStartDate });
const newStartDayShow = newStartDate.getDay();
showDays.push(newStartDayShow);
showStore.updateShowDaysField({ key: 'day', value: showDays });
};
const goBack = () => { const goBack = () => {
// showFormStore.resetShowDays();
emits("toggle-show-schedule-form"); emits("toggle-show-schedule-form");
}; };
const saveShowSchedule = () => { const saveShowSchedule = async () => {
showFormStore.saveSchedule(); if (showStore.currentShow.id) {
return await showStore.updateShowDays();
}
emits('trigger-show-creation')
}; };
const checkBoxEnd = ref(false)
const checkBoxRepetition = ref(false)
watch(checkBoxEnd, (checkBoxEnd) => { watch(checkBoxEnd, (checkBoxEnd) => {
if(checkBoxEnd == false) showFormStore.updateShowDaysField({ key: 'lastShow', value: null }) if(checkBoxEnd == false) showStore.updateShowDaysField({ key: 'lastShow', value: null })
}) })
watch(checkBoxRepetition, (checkBoxRepetition) => { watch(checkBoxRepetition, (newValue) => {
if(checkBoxRepetition == false) { if (!newValue) {
showFormStore.updateShowDaysField({key: 'day', value: []}) const startDayShow = showStore.currentShow.showDays.firstShow.getDay();
// TODO By restoring the noRepeat from the day enum showStore.updateShowDaysField({ key: 'day', value: [startDayShow] });
// one can toggle it's visibility based on the select box disabled value showStore.updateShowDaysField({ key: 'repeatType', value: 'noRepeat' });
// Only cosmetic
showFormStore.updateShowDaysField({key: 'repeatType', value: -1})
} }
}) });
// TODO Create a fixed selected day for repetition extracted from the start date
// Create a local ref var that get's the day number from the startDate, pass it
// to the days component as a fixed date
</script> </script>
<template> <template>
@ -41,18 +67,19 @@ watch(checkBoxRepetition, (checkBoxRepetition) => {
<v-card-title> <v-card-title>
<h3>Regole di programmazione</h3> <h3>Regole di programmazione</h3>
</v-card-title> </v-card-title>
<v-form> <v-form ref="form" v-model="isFormValid">
<v-card-text> <v-card-text>
<v-row no-gutters> <v-row no-gutters>
<!-- First Show Date --> <!-- First Show Date -->
<v-col cols="12" md="6" lg="4"> <v-col cols="12" md="6" lg="4">
<v-date-picker <v-date-picker
v-model="showFormStore.currentShow.showDays.firstShow" :value="showStore.currentShow.showDays.firstShow"
:min="new Date().toISOString().substring(0, 10)" :min="new Date().toISOString().substring(0, 10)"
:title="'Data inizio'" :title="'Data inizio'"
label="Data inizio" label="Data inizio"
density="compact" density="compact"
@update:modelValue="value => showFormStore.updateShowDaysField({ key: 'firstShow', value })" required="true"
v-on:update:modelValue="updateShowStartDay"
/> />
</v-col> </v-col>
@ -63,8 +90,8 @@ watch(checkBoxRepetition, (checkBoxRepetition) => {
v-model="checkBoxEnd" v-model="checkBoxEnd"
></v-checkbox> ></v-checkbox>
<v-date-picker <v-date-picker
v-model="showFormStore.currentShow.showDays.lastShow" :v-model="showStore.currentShow.showDays.lastShow"
:min="showFormStore.currentShow.showDays.firstShow" :min="showStore.currentShow.showDays.firstShow"
label="Data fine" label="Data fine"
title="Data fine" title="Data fine"
density="compact" density="compact"
@ -75,11 +102,12 @@ watch(checkBoxRepetition, (checkBoxRepetition) => {
<!-- Start Time and Duration --> <!-- Start Time and Duration -->
<v-col cols="12" md="6" lg="4"> <v-col cols="12" md="6" lg="4">
<ShowStartEndTime <ShowStartEndTime
v-model="showFormStore.currentShow.showDays.startTime" v-model="showStore.currentShow.showDays.startTime"
:start-time-label="'Ora inizio show'" :start-time-label="'Ora inizio show'"
:duration-label="'Durata'" :duration-label="'Durata'"
:end-time-label="'Orario di fine della trasmissione'" :end-time-label="'Orario di fine della trasmissione'"
@update:duration="value => showFormStore.updateShowDaysField({ key: 'duration', value })" @update:duration="value => showStore.updateShowDaysField({ key: 'duration', value })"
required="true"
/> />
</v-col> </v-col>
@ -90,7 +118,7 @@ watch(checkBoxRepetition, (checkBoxRepetition) => {
v-model="checkBoxRepetition" v-model="checkBoxRepetition"
></v-checkbox> ></v-checkbox>
<v-select <v-select
v-model="showFormStore.currentShow.showDays.repeatType" v-model="showStore.currentShow.showDays.repeatType"
:items="showRepetitionData" :items="showRepetitionData"
label="Ripetizione" label="Ripetizione"
density="compact" density="compact"
@ -100,10 +128,11 @@ watch(checkBoxRepetition, (checkBoxRepetition) => {
/> />
<DaysCheckbox <DaysCheckbox
v-model="showFormStore.currentShow.showDays.day" v-model="showStore.currentShow.showDays.day"
:disabled="!checkBoxRepetition || (showStore.currentShow.showDays.repeatType === 'noRepeat')"
:fixedDay="showStore.currentShow.showDays.firstShow.getDay()"
@update:modelValue="value => showStore.updateShowDaysField({ key: 'day', value })"
label="Giorni" label="Giorni"
:disabled="!checkBoxRepetition"
@update:modelValue="value => showFormStore.updateShowDaysField({ key: 'day', value })"
/> />
</v-col> </v-col>
</v-row> </v-row>
@ -111,7 +140,7 @@ watch(checkBoxRepetition, (checkBoxRepetition) => {
<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 color="accent" @click="saveShowSchedule">Salva</v-btn> <v-btn color="accent" @click="saveShowSchedule" :disabled="!isFormValid">Salva</v-btn>
</v-card-actions> </v-card-actions>
</v-form> </v-form>
</v-card> </v-card>

View file

@ -1,33 +1,31 @@
enum ShowRepeatEnum { // TODO i18n strings
Weekly = 0, export enum ShowRepeatEnum {
Biweekly = 1, weekly = 'Weekly',
Triweekly = 4, biweekly = 'Biweekly',
FourWeeks = 5, triweekly = 'Triweekly',
Monthly = 2, fourWeeks = 'Four weeks',
monthly = 'Monthly',
noRepeat = 'No repeat',
} }
export interface ShowRepetitionType { export interface ShowRepetitionType {
type: ShowRepeatEnum; type: ShowRepeatEnum;
repeatName: string
} }
export const showRepetitionData: ShowRepetitionType[] = [ export const showRepetitionData: ShowRepetitionType[] = Object.entries(ShowRepeatEnum).map(([key, value]) => ({
{ type: ShowRepeatEnum.Weekly, repeatName: "Weekly" }, type: key as ShowRepeatEnum,
{ type: ShowRepeatEnum.Biweekly, repeatName: "Biweekly" }, repeatName: value,
{ type: ShowRepeatEnum.Triweekly, repeatName: "Triweekly" }, }));
{ type: ShowRepeatEnum.FourWeeks, repeatName: "Four Weeks" },
{ type: ShowRepeatEnum.Monthly, repeatName: "Monthly" },
];
enum WeekDaysEnum { enum WeekDaysEnum {
Monday = 0, Monday = 1,
Tuesday = 1, Tuesday = 2,
Wednesday = 2, Wednesday = 3,
Thursday = 3, Thursday = 4,
Friday = 4, Friday = 5,
Saturday = 5, Saturday = 6,
Sunday = 6, Sunday = 0,
} }
export interface WeekDays { export interface WeekDays {

View file

@ -1,6 +1,6 @@
import type {ShowInstance} from "@models/show/showInstance.ts"; import type {ShowInstance} from "@models/show/showInstance.ts";
import type {ShowDays} from "@models/show/showDays"; import type {ShowDays} from "@models/show/showDays";
import type {ShowDjs} from "@models/show/showDjs"; import type {ShowDJs} from "@models/show/showDJs.ts";
import axios, {type AxiosResponse} from "axios"; import axios, {type AxiosResponse} from "axios";
import {cleanOptions} from "@/helpers/AxiosHelper.ts"; import {cleanOptions} from "@/helpers/AxiosHelper.ts";
@ -18,13 +18,13 @@ export interface Show {
liveStreamPass?: string; liveStreamPass?: string;
imagePath?: string | File; imagePath?: string | File;
hasAutoplaylist: boolean; hasAutoplaylist: boolean;
autoplaylist_id?: number; autoplaylistId?: number;
autoplaylistRepeat: boolean; autoplaylistRepeat: boolean;
// Relationships // Relationships
block?: any; block?: any;
showDays?: ShowDays; showDays?: ShowDays;
showDjs?: ShowDjs[]; showDjs?: ShowDJs[];
showInstances?: ShowInstance[]; showInstances?: ShowInstance[];
playlist?: any; playlist?: any;
} }
@ -32,7 +32,7 @@ export interface Show {
export const baseShow = (): Show => { export const baseShow = (): Show => {
return { return {
id: null, id: null,
name: '', name: 'Esempio',
url: '', url: '',
genre: '', genre: '',
description: '', description: '',
@ -42,7 +42,7 @@ export const baseShow = (): Show => {
liveStreamPass: '', liveStreamPass: '',
imagePath: '', imagePath: '',
hasAutoplaylist: false, hasAutoplaylist: false,
autoplaylist_id: 0, autoplaylistId: null,
autoplaylistRepeat: false, autoplaylistRepeat: false,
showDjs: null, showDjs: null,
showDays: null, showDays: null,

View file

@ -1,6 +1,6 @@
import { VTextField } from "vuetify/components"; import { VTextField } from "vuetify/components";
export interface ShowDjs { export interface ShowDJs {
id?: number; id?: number;
subjsId: number; subjsId: number;
showId: number; showId: number;
@ -11,7 +11,7 @@ export interface User {
login: string; login: string;
} }
export function showDjsForm(item: ShowDjs) { export function showDjsForm(item: ShowDJs) {
const visibleFields = { const visibleFields = {
subjsId: 'Presentatore', subjsId: 'Presentatore',
showId: 'Programma' showId: 'Programma'
@ -22,7 +22,7 @@ export function showDjsForm(item: ShowDjs) {
Object.keys(visibleFields).forEach((key) => { Object.keys(visibleFields).forEach((key) => {
fields[key] = { fields[key] = {
label: visibleFields[key], label: visibleFields[key],
value: item[key as keyof ShowDjs], value: item[key as keyof ShowDJs],
component: VTextField, component: VTextField,
disabled: false disabled: false
}; };

View file

@ -1,6 +1,8 @@
import axios, {type AxiosResponse} from "axios"; import axios, {type AxiosResponse} from "axios";
import {cleanOptions} from "@/helpers/AxiosHelper.ts"; import {cleanOptions, snakeToCamel} from "@/helpers/AxiosHelper.ts";
import type {ShowRepetitionType} from "@models/show/ShowRepetition.ts";
//TODO RepeatType should use the interface ShowRepetitionType
export interface ShowDays { export interface ShowDays {
id?: number; id?: number;
firstShow: Date; firstShow: Date;
@ -9,7 +11,7 @@ export interface ShowDays {
timezone: string; timezone: string;
duration: string; duration: string;
day: number[]; day: number[];
repeatType: number; repeatType: string;
nextPopDate?: string; nextPopDate?: string;
showId?: number; showId?: number;
record?: number; record?: number;
@ -22,8 +24,8 @@ export const baseShowDays = ():ShowDays => {
startTime: '08:00', startTime: '08:00',
timezone: '', timezone: '',
duration: '01:00', duration: '01:00',
day: [0], day: [],
repeatType: 0, repeatType: 'noRepeat',
nextPopDate: '', nextPopDate: '',
showId: 0, showId: 0,
record: 0 record: 0
@ -31,14 +33,14 @@ export const baseShowDays = ():ShowDays => {
} }
export const getShowDays = async (options: { export const getShowDays = async (options: {
showId?: number | null; show_id?: number | null;
flattenShowDays?: boolean; flattenShowDays?: boolean;
}): Promise<ShowDays> => { }): Promise<ShowDays> => {
const filteredParams = cleanOptions(options); const filteredParams = cleanOptions(options);
return await axios.get(`/showDays`, {params: filteredParams}) return await axios.get(`/showDays`, {params: filteredParams})
.then((response: AxiosResponse) => { .then((response: AxiosResponse) => {
return response.data return snakeToCamel(response.data);
}).catch((error: Error) => { }).catch((error: Error) => {
console.log("Error: " + error); console.log("Error: " + error);
}) })

View file

@ -2,4 +2,30 @@ export function cleanOptions(options: Object): Object {
return Object.fromEntries( return Object.fromEntries(
Object.entries(options).filter(([_, value]) => value !== undefined && value !== null) Object.entries(options).filter(([_, value]) => value !== undefined && value !== null)
); );
} }
export function camelToSnake(obj) {
if (Array.isArray(obj)) {
return obj.map(camelToSnake);
} else if (obj !== null && typeof obj === 'object') {
return Object.keys(obj).reduce((acc, key) => {
const snakeKey = key.replace(/([A-Z])/g, '_$1').toLowerCase();
acc[snakeKey] = camelToSnake(obj[key]);
return acc;
}, {});
}
return obj;
}
export function snakeToCamel(obj) {
if (Array.isArray(obj)) {
return obj.map(snakeToCamel);
} else if (obj !== null && typeof obj === 'object') {
return Object.keys(obj).reduce((acc, key) => {
const camelKey = key.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
acc[camelKey] = snakeToCamel(obj[key]);
return acc;
}, {});
}
return obj;
}

View file

@ -9,5 +9,23 @@ export function extractTime(dateTimeString: string, extractTime: boolean = false
const minutes = String(date.getMinutes()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0');
return `${hours}:${minutes}`; return `${hours}:${minutes}`;
} }
return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate(); return date.getFullYear() + '-' + String(date.getMonth() + 1).padStart(2, '0') + '-' + String(date.getDate()).padStart(2, '0');
}; };
export function getTimeDiff(startTime: Date, endTime: Date) {
const diffInMilliseconds = Math.abs(startTime.getTime() - endTime.getTime());
const hours = Math.floor((diffInMilliseconds % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diffInMilliseconds % (1000 * 60 * 60)) / (1000 * 60));
const formattedHours = String(hours).padStart(2, '0');
const formattedMinutes = String(minutes).padStart(2, '0');
return `${formattedHours}:${formattedMinutes}`;
}
export function getHoursMinutesFromString(timeString: string): Date {
const [ h, m ] = timeString.split(":");
const ms = new Date().setHours(h,m);
return new Date(ms)
}

View file

@ -1,6 +1,9 @@
import { defineStore } from 'pinia' import {defineStore} from 'pinia'
import { type Show } from '@models/show/show' import {type Show} from '@models/show/show'
import type {ShowDays} from "@models/show/showDays"; import type {ShowDays} from "@models/show/showDays";
import axios, {type AxiosResponse} from "axios";
import {camelToSnake} from "@/helpers/AxiosHelper.ts";
import {extractTime} from "@/helpers/DateFormatter.ts";
export const useShowStore = defineStore('show', { export const useShowStore = defineStore('show', {
state: () => ({ state: () => ({
@ -9,23 +12,46 @@ export const useShowStore = defineStore('show', {
}), }),
actions: { actions: {
async loadShow(showData: Show) { async loadShow(showData: Show) {
this.currentShow = { ...showData } this.currentShow = {...showData}
}, },
updateField(payload: { key: string; value: any }) { updateField(payload: { key: string; value: any }) {
this.currentShow[payload.key] = payload.value this.currentShow[payload.key] = payload.value
}, },
async saveShow() {
// Implement API call to save this.currentShow
},
async saveSchedule() {
// Implement schedule saving logic
},
resetShowDays() {
this.currentShow.showDays = { ...this.baseShowDays };
},
updateShowDaysField(payload: { key: string; value: any }) { updateShowDaysField(payload: { key: string; value: any }) {
this.currentShow.showDays[payload.key] = payload.value; this.currentShow.showDays[payload.key] = payload.value;
} },
async createShow() {
let showData = {...this.currentShow};
showData.showDays.firstShow = extractTime(showData.showDays.firstShow);
if (showData.showDays.lastShow) {
showData.showDays.lastShow = extractTime(showData.showDays.lastShow);
}
showData = camelToSnake(showData);
return await axios.post(`/show`, showData)
.then((response: AxiosResponse) => {
return response.data
}).catch((error: Error) => {
console.log("Error: " + error);
})
},
async updateShow() {
return await axios.post(`/show`, this.currentShow)
.then((response: AxiosResponse) => {
return response.data
}).catch((error: Error) => {
console.log("Error: " + error.message);
})
},
async updateShowDays() {
return await axios.post(`/showDays`, this.currentShow.showDays)
.then((response: AxiosResponse) => {
return response.data
}).catch((error: Error) => {
console.log("Error: " + error);
})
},
resetShowDays() {
this.currentShow.showDays = {...this.baseShowDays};
},
} }
}) })

View file

@ -18,7 +18,10 @@ export const useShowInstanceStore = defineStore('showInstance', {
this.currentShowInstance = baseShowInstance() this.currentShowInstance = baseShowInstance()
}, },
async saveShowInstance() { async saveShowInstance() {
// Implement API call to save this.currentShowInstance
},
async updateShowInstance() {
}, },
} }
}) })

View file

@ -3,6 +3,7 @@
use App\Http\Controllers\Auth\LoginController; use App\Http\Controllers\Auth\LoginController;
use App\Http\Controllers\FileController; use App\Http\Controllers\FileController;
use App\Http\Controllers\Show\ShowController; use App\Http\Controllers\Show\ShowController;
use App\Http\Controllers\Show\ShowDaysController;
use App\Http\Controllers\ShowInstance\ShowInstancesController; use App\Http\Controllers\ShowInstance\ShowInstancesController;
use App\Http\Controllers\SmartBlockController; use App\Http\Controllers\SmartBlockController;
use App\Http\Controllers\TrackTypeController; use App\Http\Controllers\TrackTypeController;
@ -33,6 +34,7 @@ Route::resources([
'playlist' => PlaylistController::class, 'playlist' => PlaylistController::class,
'smartblock' => SmartBlockController::class, 'smartblock' => SmartBlockController::class,
'show' => ShowController::class, 'show' => ShowController::class,
'showDays' => ShowDaysController::class,
'showInstances' => ShowInstancesController::class, 'showInstances' => ShowInstancesController::class,
'user' => UserController::class, 'user' => UserController::class,
], [ ], [