2026-02-04 15:26:44 -05:00
|
|
|
{#
|
|
|
|
|
/**
|
|
|
|
|
* Copyright (C) 2023 Xibo Signage Ltd
|
|
|
|
|
*
|
|
|
|
|
* Xibo - Digital Signage - http://www.xibo.org.uk
|
|
|
|
|
*
|
|
|
|
|
* This file is part of Xibo.
|
|
|
|
|
*
|
|
|
|
|
* Xibo is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
* any later version.
|
|
|
|
|
*
|
|
|
|
|
* Xibo is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
|
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
#}
|
|
|
|
|
{% extends "authed.twig" %}
|
|
|
|
|
{% import "inline.twig" as inline %}
|
|
|
|
|
{% import "forms.twig" as forms %}
|
|
|
|
|
|
|
|
|
|
{% block title %}{{ "Schedule"|trans }} | {% endblock %}
|
|
|
|
|
|
Add new pages for managing tags, tasks, transitions, users, user groups, and their respective JavaScript functionalities
- Implemented tag management page with filtering, data table, and AJAX functionality.
- Created task management page with task listing, filtering, and AJAX data loading.
- Developed transition management page with a data table for transitions.
- Added user management page with comprehensive user details, filtering options, and AJAX support.
- Introduced user group management page with filtering and data table for user groups.
- Enhanced JavaScript for data tables, including state saving, filtering, and AJAX data fetching for all new pages.
2026-02-06 23:54:21 -05:00
|
|
|
{% block actionMenu %}{% endblock %}
|
2026-02-04 15:26:44 -05:00
|
|
|
|
|
|
|
|
{% block pageContent %}
|
2026-02-11 20:47:09 -05:00
|
|
|
<div class="ots-static-page ots-displays-page">
|
2026-02-04 15:26:44 -05:00
|
|
|
<div class="page-header ots-page-header">
|
|
|
|
|
<h1>{% trans "Schedule" %}</h1>
|
|
|
|
|
<p class="text-muted">{% trans "Schedule content to your displays." %}</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
Refactor dashboard card classes to use 'content-card' instead of 'dashboard-card'
- Updated various views to replace 'dashboard-card' with 'content-card' for consistency in styling.
- Modified filter and table card classes across multiple pages including applications, campaigns, commands, datasets, dayparts, displays, display groups, display profiles, fonts, layouts, libraries, menu boards, modules, player software, playlists, resolutions, schedules, settings, sync groups, tags, tasks, templates, transitions, users, and user groups.
2026-02-11 09:17:45 -05:00
|
|
|
<div class="widget content-card ots-displays-card">
|
2026-02-04 15:26:44 -05:00
|
|
|
<div class="widget-body ots-displays-body">
|
|
|
|
|
<div class="XiboGrid" id="{{ random() }}" data-grid-name="scheduleGridView">
|
Refactor dashboard card classes to use 'content-card' instead of 'dashboard-card'
- Updated various views to replace 'dashboard-card' with 'content-card' for consistency in styling.
- Modified filter and table card classes across multiple pages including applications, campaigns, commands, datasets, dayparts, displays, display groups, display profiles, fonts, layouts, libraries, menu boards, modules, player software, playlists, resolutions, schedules, settings, sync groups, tags, tasks, templates, transitions, users, and user groups.
2026-02-11 09:17:45 -05:00
|
|
|
<div class="XiboFilter card mb-3 bg-light content-card ots-filter-card">
|
2026-02-04 15:26:44 -05:00
|
|
|
<div class="ots-filter-header">
|
|
|
|
|
<h3 class="ots-filter-title">{% trans "Filter Schedule" %}</h3>
|
|
|
|
|
<button type="button" class="ots-filter-toggle" id="ots-filter-collapse-btn" title="{% trans 'Toggle filter panel' %}">
|
2026-02-05 09:04:06 -05:00
|
|
|
<i class="fa fa-chevron-down"></i>
|
2026-02-04 15:26:44 -05:00
|
|
|
</button>
|
|
|
|
|
</div>
|
2026-02-05 09:04:06 -05:00
|
|
|
<div class="ots-filter-content collapsed" id="ots-filter-content">
|
2026-02-04 15:26:44 -05:00
|
|
|
<div class="FilterDiv card-body" id="schedule-filter">
|
|
|
|
|
<ul class="nav nav-tabs" role="tablist">
|
|
|
|
|
<li class="nav-item"><a class="nav-link active" href="#general-filter" role="tab" data-toggle="tab" aria-selected="true"><span>{% trans "General" %}</span></a></li>
|
|
|
|
|
<li class="nav-item"><a class="nav-link" href="#advanced-filter" role="tab" data-toggle="tab" aria-selected="false"><span>{% trans "Advanced" %}</span></a></li>
|
|
|
|
|
</ul>
|
|
|
|
|
<form class="form-inline">
|
|
|
|
|
<div class="tab-content">
|
|
|
|
|
<div class="tab-pane active" id="general-filter" role="tabpanel">
|
|
|
|
|
{% set title %}{% trans "Range" %}{% endset %}
|
|
|
|
|
{% set range %}{% trans "Custom" %}{% endset %}
|
|
|
|
|
{% set day %}{% trans "Day" %}{% endset %}
|
|
|
|
|
{% set week %}{% trans "Week" %}{% endset %}
|
|
|
|
|
{% set month %}{% trans "Month" %}{% endset %}
|
|
|
|
|
{% set year %}{% trans "Year" %}{% endset %}
|
|
|
|
|
{% set options = [
|
|
|
|
|
{ name: "custom", range: range },
|
|
|
|
|
{ name: "day", range: day },
|
|
|
|
|
{ name: "week", range: week },
|
|
|
|
|
{ name: "month", range: month },
|
|
|
|
|
{ name: "year", range: year },
|
|
|
|
|
] %}
|
|
|
|
|
{{ inline.dropdown("range", "single", title, "month", options, "name", "range", "", "date-range-input") }}
|
|
|
|
|
|
|
|
|
|
{% set title %}{% trans 'From Date' %}{% endset %}
|
|
|
|
|
{{ inline.dateTime("fromDt", title, "", "", "custom-date-range d-none", "", "") }}
|
|
|
|
|
|
|
|
|
|
{% set title %}{% trans 'To Date' %}{% endset %}
|
|
|
|
|
{{ inline.dateTime("toDt", title, "", "", "custom-date-range d-none", "", "") }}
|
|
|
|
|
|
|
|
|
|
{% set title %}{% trans "Date Controls" %}{% endset %}
|
|
|
|
|
<div class="form-group mr-1 mb-1 controls-date-range">
|
|
|
|
|
<div class="control-label mr-1" title=""
|
|
|
|
|
accesskey="">{{ title }}</div>
|
|
|
|
|
<div class="controls-date-inputs">
|
|
|
|
|
<div class="inputgroup date" id="dateInput">
|
|
|
|
|
<span class="btn btn-outline-primary date-open-button" role="button">
|
|
|
|
|
<i class="fa fa-calendar"></i>
|
|
|
|
|
</span>
|
|
|
|
|
<input type="text" class="form-control" id="dateInputLink" data-input style="display:none;"/>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="btn-group">
|
|
|
|
|
<button type="button" class="btn btn-secondary" data-calendar-nav="prev"><span class="fa fa-caret-left"></span> {% trans "Prev" %}</button>
|
|
|
|
|
<button type="button" class="btn btn-outline-secondary" data-calendar-nav="today">{% trans "Today" %}</button>
|
|
|
|
|
<button type="button" class="btn btn-secondary" data-calendar-nav="next">{% trans "Next" %} <span class="fa fa-caret-right"></span></button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{% set title %}{% trans "Name" %}{% endset %}
|
|
|
|
|
{{ inline.inputNameGrid('name', title) }}
|
|
|
|
|
|
|
|
|
|
{% set title %}{% trans 'Event Type' %}{% endset %}
|
|
|
|
|
{{ inline.dropdown("eventTypeId", "single", title, "", [{eventTypeId: null, eventTypeName: "All"}]|merge(eventTypes), "eventTypeId", "eventTypeName") }}
|
|
|
|
|
|
|
|
|
|
{% set title %}{% trans "Layout / Campaign" %}{% endset %}
|
|
|
|
|
{% set helpText %}{% trans "Please select a Layout or Campaign for this Event to show" %}{% endset %}
|
|
|
|
|
|
|
|
|
|
<div class="form-group mr-1 mb-1">
|
|
|
|
|
<label class="control-label mr-1" for="campaignId" title=""
|
|
|
|
|
accesskey="">{{ title }}</label>
|
|
|
|
|
<select name="campaignId" id="campaignIdFilter" class="form-control"
|
|
|
|
|
data-search-url="{{ url_for("campaign.search") }}"
|
|
|
|
|
data-trans-campaigns="{% trans "Campaigns" %}"
|
|
|
|
|
data-trans-layouts="{% trans "Layouts" %}"
|
|
|
|
|
data-allow-clear="true"
|
|
|
|
|
data-width="100%"
|
|
|
|
|
title="{% trans "Layout / Campaign" %}"
|
|
|
|
|
data-placeholder="{% trans "Layout / Campaign" %}"
|
|
|
|
|
data-dropdownAutoWidth
|
|
|
|
|
>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{% set title %}{% trans "Displays" %}{% endset %}
|
Add new pages for managing tags, tasks, transitions, users, user groups, and their respective JavaScript functionalities
- Implemented tag management page with filtering, data table, and AJAX functionality.
- Created task management page with task listing, filtering, and AJAX data loading.
- Developed transition management page with a data table for transitions.
- Added user management page with comprehensive user details, filtering options, and AJAX support.
- Introduced user group management page with filtering and data table for user groups.
- Enhanced JavaScript for data tables, including state saving, filtering, and AJAX data fetching for all new pages.
2026-02-06 23:54:21 -05:00
|
|
|
<div class="form-group mr-1 mb-1 pagedSelect">
|
2026-02-04 15:26:44 -05:00
|
|
|
<label class="control-label mr-1" for="DisplayList" title=""
|
|
|
|
|
accesskey="">{{ title }}</label>
|
|
|
|
|
<select id="DisplayList" class="form-control" name="displaySpecificGroupIds[]"
|
|
|
|
|
data-width="100%"
|
|
|
|
|
data-placeholder="{% trans "Displays" %}"
|
|
|
|
|
data-search-url="{{ url_for("display.search") }}"
|
|
|
|
|
data-search-term="display"
|
|
|
|
|
data-id-property="displayGroupId"
|
|
|
|
|
data-text-property="display"
|
|
|
|
|
data-additional-property="displayGroupId"
|
|
|
|
|
data-allow-clear="true"
|
|
|
|
|
data-initial-key="displayGroupIds[]"
|
|
|
|
|
multiple>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{% set title %}{% trans "Display Groups" %}{% endset %}
|
Add new pages for managing tags, tasks, transitions, users, user groups, and their respective JavaScript functionalities
- Implemented tag management page with filtering, data table, and AJAX functionality.
- Created task management page with task listing, filtering, and AJAX data loading.
- Developed transition management page with a data table for transitions.
- Added user management page with comprehensive user details, filtering options, and AJAX support.
- Introduced user group management page with filtering and data table for user groups.
- Enhanced JavaScript for data tables, including state saving, filtering, and AJAX data fetching for all new pages.
2026-02-06 23:54:21 -05:00
|
|
|
<div class="form-group mr-2 mb-1 pagedSelect">
|
2026-02-04 15:26:44 -05:00
|
|
|
<label class="control-label mr-1" for="DisplayGroupList" title=""
|
|
|
|
|
accesskey="">{{ title }}</label>
|
|
|
|
|
<select id="DisplayGroupList" class="form-control" name="displayGroupIds[]"
|
|
|
|
|
data-width="100%"
|
|
|
|
|
data-placeholder="{% trans "Display Groups" %}"
|
|
|
|
|
data-search-url="{{ url_for("displayGroup.search") }}"
|
|
|
|
|
data-search-term="displayGroup"
|
|
|
|
|
data-id-property="displayGroupId"
|
|
|
|
|
data-text-property="displayGroup"
|
|
|
|
|
data-allow-clear="true"
|
|
|
|
|
data-initial-key="displayGroupIds[]"
|
|
|
|
|
multiple>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="tab-pane" id="advanced-filter" role="tabpanel">
|
|
|
|
|
{% set label %}{% trans "Direct Schedule?" %}{% endset %}
|
|
|
|
|
{% set title %}{% trans "Show only events scheduled directly on selected Displays/Groups" %}{% endset %}
|
|
|
|
|
<div class="form-group ml-2 mr-3 mb-1">
|
|
|
|
|
<div class="form-check">
|
|
|
|
|
<input title="{{ title }}" class="form-check-input" type="checkbox" id="directSchedule" name="directSchedule">
|
|
|
|
|
<label class="form-check-label" title="{{ title }}" for="directSchedule" accesskey="">{{ label }}</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{% set title %}{% trans "Only show schedules which appear on all filtered displays/groups?" %}{% endset %}
|
|
|
|
|
{% set label %}{% trans "Shared Schedule?" %}{% endset %}
|
|
|
|
|
<div class="form-group ml-2 mr-3 mb-1">
|
|
|
|
|
<div class="form-check">
|
|
|
|
|
<input title="{{ title }}" class="form-check-input" type="checkbox" id="sharedSchedule" name="sharedSchedule">
|
|
|
|
|
<label class="form-check-label" title="{{ title }}" for="sharedSchedule" accesskey="">{{ label }}</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{% set title %}{% trans 'Geo Aware?' %}{% endset %}
|
|
|
|
|
{% set options = [
|
|
|
|
|
{ id: null, name: "Both"|trans },
|
|
|
|
|
{ id: 0, name: "No"|trans },
|
|
|
|
|
{ id: 1, name: "Yes"|trans }
|
|
|
|
|
] %}
|
|
|
|
|
{{ inline.dropdown("geoAware", "single", title, "both", options, "id", "name") }}
|
|
|
|
|
|
|
|
|
|
{% set title %}{% trans 'Recurring?' %}{% endset %}
|
|
|
|
|
{% set options = [
|
|
|
|
|
{ id: null, name: "Both" },
|
|
|
|
|
{ id: 0, name: "No"|trans },
|
|
|
|
|
{ id: 1, name: "Yes"|trans }
|
|
|
|
|
] %}
|
|
|
|
|
{{ inline.dropdown("recurring", "single", title, "both", options, "id", "name") }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
Refactor dashboard card classes to use 'content-card' instead of 'dashboard-card'
- Updated various views to replace 'dashboard-card' with 'content-card' for consistency in styling.
- Modified filter and table card classes across multiple pages including applications, campaigns, commands, datasets, dayparts, displays, display groups, display profiles, fonts, layouts, libraries, menu boards, modules, player software, playlists, resolutions, schedules, settings, sync groups, tags, tasks, templates, transitions, users, and user groups.
2026-02-11 09:17:45 -05:00
|
|
|
<div class="XiboSchedule card content-card ots-table-card">
|
Add new pages for managing tags, tasks, transitions, users, user groups, and their respective JavaScript functionalities
- Implemented tag management page with filtering, data table, and AJAX functionality.
- Created task management page with task listing, filtering, and AJAX data loading.
- Developed transition management page with a data table for transitions.
- Added user management page with comprehensive user details, filtering options, and AJAX support.
- Introduced user group management page with filtering and data table for user groups.
- Enhanced JavaScript for data tables, including state saving, filtering, and AJAX data fetching for all new pages.
2026-02-06 23:54:21 -05:00
|
|
|
<div class="ots-table-toolbar">
|
|
|
|
|
{% if currentUser.featureEnabled("schedule.add") %}
|
Refactor toolbar buttons across various pages to unify styling
- Updated button classes for consistency in the playersoftware-page, playlist-page, resolution-page, schedule-page, settings-page, syncgroup-page, tag-page, task-page, template-page, transition-page, user-page, and usergroup-page.
- Removed unnecessary text from button titles and ensured all buttons have the 'ots-toolbar-btn' class for uniformity.
- Cleaned up the code by removing commented-out sections and ensuring proper indentation.
2026-02-07 14:50:40 -05:00
|
|
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add a new Scheduled event" %}" href="{{ url_for("schedule.add.form") }}"><i class="fa fa-plus" aria-hidden="true"></i></button>
|
Add new pages for managing tags, tasks, transitions, users, user groups, and their respective JavaScript functionalities
- Implemented tag management page with filtering, data table, and AJAX functionality.
- Created task management page with task listing, filtering, and AJAX data loading.
- Developed transition management page with a data table for transitions.
- Added user management page with comprehensive user details, filtering options, and AJAX support.
- Introduced user group management page with filtering and data table for user groups.
- Enhanced JavaScript for data tables, including state saving, filtering, and AJAX data fetching for all new pages.
2026-02-06 23:54:21 -05:00
|
|
|
{% endif %}
|
Refactor toolbar buttons across various pages to unify styling
- Updated button classes for consistency in the playersoftware-page, playlist-page, resolution-page, schedule-page, settings-page, syncgroup-page, tag-page, task-page, template-page, transition-page, user-page, and usergroup-page.
- Removed unnecessary text from button titles and ensured all buttons have the 'ots-toolbar-btn' class for uniformity.
- Cleaned up the code by removing commented-out sections and ensuring proper indentation.
2026-02-07 14:50:40 -05:00
|
|
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
Add new pages for managing tags, tasks, transitions, users, user groups, and their respective JavaScript functionalities
- Implemented tag management page with filtering, data table, and AJAX functionality.
- Created task management page with task listing, filtering, and AJAX data loading.
- Developed transition management page with a data table for transitions.
- Added user management page with comprehensive user details, filtering options, and AJAX support.
- Introduced user group management page with filtering and data table for user groups.
- Enhanced JavaScript for data tables, including state saving, filtering, and AJAX data fetching for all new pages.
2026-02-06 23:54:21 -05:00
|
|
|
</div>
|
2026-02-04 15:26:44 -05:00
|
|
|
<div class="card-header">
|
|
|
|
|
<ul class="nav nav-tabs card-header-tabs">
|
|
|
|
|
<li class="nav-item">
|
|
|
|
|
<a class="schedule-nav grid-nav nav-link active" id="grid-tab" href="#grid-view"
|
|
|
|
|
data-schedule-view="grid"
|
|
|
|
|
role="tab"
|
|
|
|
|
data-toggle="tab"><span>{% trans "Grid" %}</span></a>
|
|
|
|
|
</li>
|
|
|
|
|
<li class="nav-item">
|
|
|
|
|
<a class="schedule-nav calendar-nav nav-link" id="calendar-tab" href="#calendar-view"
|
|
|
|
|
data-schedule-view="calendar"
|
|
|
|
|
data-calendar-view="month"
|
|
|
|
|
role="tab"
|
|
|
|
|
data-toggle="tab"><span>{% trans "Calendar" %}</span></a>
|
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="card-body">
|
2026-02-11 20:47:09 -05:00
|
|
|
<div class="xibo-calendar-header-container col-xl-12">
|
|
|
|
|
<div class="ots-calendar-nav">
|
|
|
|
|
<button type="button" class="ots-cal-arrow ots-cal-arrow-prev" id="ots-cal-prev" title="{% trans 'Previous' %}">
|
|
|
|
|
<i class="fa fa-chevron-left"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<div class="xibo-calendar-header text-center">
|
|
|
|
|
<h1 class="page-header"></h1>
|
|
|
|
|
<div class="calendar-loading">
|
|
|
|
|
<span id="calendar-progress-table" class="fa fa-spin fa-cog"></span>
|
|
|
|
|
<span id="calendar-progress" class="fa fa-spin fa-cog"></span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<button type="button" class="ots-cal-arrow ots-cal-arrow-next" id="ots-cal-next" title="{% trans 'Next' %}">
|
|
|
|
|
<i class="fa fa-chevron-right"></i>
|
|
|
|
|
</button>
|
2026-02-04 15:26:44 -05:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="tab-content">
|
|
|
|
|
<div class="tab-pane active" id="grid-view">
|
|
|
|
|
<div class="XiboData pt-3">
|
|
|
|
|
<table id="schedule-grid" class="table table-striped w-100"
|
|
|
|
|
data-state-preference-name="scheduleGrid">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>{% trans 'ID' %}</th>
|
|
|
|
|
<th></th>
|
|
|
|
|
<th>{% trans 'Event Type' %}</th>
|
|
|
|
|
<th>{% trans 'Name' %}</th>
|
|
|
|
|
<th>{% trans 'Start' %}</th>
|
|
|
|
|
<th>{% trans 'End' %}</th>
|
|
|
|
|
<th>{% trans 'Event' %}</th>
|
|
|
|
|
<th>{% trans 'Campaign ID' %}</th>
|
|
|
|
|
<th>{% trans 'Display Groups' %}</th>
|
|
|
|
|
<th>{% trans 'SoV' %}</th>
|
|
|
|
|
<th>{% trans 'Max Plays per Hour' %}</th>
|
|
|
|
|
<th>{% trans 'Geo Aware?' %}</th>
|
|
|
|
|
<th>{% trans 'Recurring?' %}</th>
|
|
|
|
|
<th>{% trans 'Recurrence Description' %}</th>
|
|
|
|
|
<th>{% trans 'Recurrence Type' %}</th>
|
|
|
|
|
<th>{% trans 'Recurrence Interval' %}</th>
|
|
|
|
|
<th>{% trans 'Recurrence Repeats On' %}</th>
|
|
|
|
|
<th>{% trans 'Recurrence End' %}</th>
|
|
|
|
|
<th>{% trans 'Priority?' %}</th>
|
|
|
|
|
<th>{% trans 'Criteria?' %}</th>
|
|
|
|
|
<th>{% trans 'Created On' %}</th>
|
|
|
|
|
<th>{% trans 'Updated On' %}</th>
|
|
|
|
|
<th>{% trans 'Modified By' %}</th>
|
|
|
|
|
<th class="rowMenu"></th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="tab-pane" id="calendar-view">
|
2026-03-23 21:09:27 -04:00
|
|
|
{# ── OTS FullCalendar container (replaces legacy CalendarContainer visually) ── #}
|
|
|
|
|
<div id="ots-fullcalendar"></div>
|
|
|
|
|
|
|
|
|
|
{# Legacy container kept hidden so Xibo bundle doesn't error.
|
|
|
|
|
NOTE: id="Calendar" is intentionally omitted to prevent the Xibo bundle
|
|
|
|
|
from initialising the legacy calendar and throwing _loadEvents errors.
|
|
|
|
|
The data-agenda-link is preserved on CalendarContainer for our event-click handler. #}
|
|
|
|
|
<div class="row d-none" id="ots-legacy-calendar-row">
|
2026-02-04 15:26:44 -05:00
|
|
|
<div id="CalendarContainer"
|
|
|
|
|
data-agenda-link="{{ url_for("schedule.events", {id: ':id'}) }}"
|
|
|
|
|
data-calendar-type="{{ settings.CALENDAR_TYPE }}" class="col-sm-12"
|
|
|
|
|
data-default-lat="{{ defaultLat }}"
|
|
|
|
|
data-default-long="{{ defaultLong }}">
|
2026-03-23 21:09:27 -04:00
|
|
|
<div class="calendar-view" id="ots-legacy-calendar-stub"></div>
|
2026-02-04 15:26:44 -05:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-03-23 21:09:27 -04:00
|
|
|
|
2026-02-04 15:26:44 -05:00
|
|
|
<div class="row">
|
|
|
|
|
<div class="col-sm-12">
|
|
|
|
|
<div class="cal-legend">
|
|
|
|
|
<ul>
|
2026-03-23 21:09:27 -04:00
|
|
|
<li><span class="fa fa-retweet" style="color:#6366f1"></span> {% trans "Always showing" %}</li>
|
|
|
|
|
<li><span class="fa fa-desktop" style="color:#0ea5e9"></span> {% trans "Single Display" %}</li>
|
|
|
|
|
<li><span class="fa fa-desktop" style="color:#10b981"></span> {% trans "Multi Display" %}</li>
|
|
|
|
|
<li><span class="fa fa-bullseye" style="color:#ef4444"></span> {% trans "Priority" %}</li>
|
|
|
|
|
<li><span class="fa fa-repeat" style="color:#8b5cf6"></span> {% trans "Recurring" %}</li>
|
|
|
|
|
<li><span class="fa fa-lock" style="color:#64748b"></span> {% trans "View Only" %}</li>
|
|
|
|
|
<li><span class="fa fa-wrench" style="color:#f59e0b"></span> {% trans "Command" %}</li>
|
|
|
|
|
<li><span class="fa fa-hand-paper" style="color:#f97316"></span> {% trans "Interrupt" %}</li>
|
|
|
|
|
<li><span class="fa fa-map-marker" style="color:#14b8a6"></span> {% trans "Geo Location" %}</li>
|
|
|
|
|
<li><span class="fa fa-paper-plane" style="color:#ec4899"></span> {% trans "Interactive Action" %}</li>
|
|
|
|
|
<li><span class="fa fa-refresh" style="color:#06b6d4"></span> {% trans "Synchronised" %}</li>
|
2026-02-04 15:26:44 -05:00
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{% endblock %}
|
|
|
|
|
|
|
|
|
|
{% block javaScript %}
|
|
|
|
|
{# Initialise JS variables #}
|
|
|
|
|
<script type="text/javascript" nonce="{{ cspNonce }}">
|
|
|
|
|
{# JS variables #}
|
|
|
|
|
var scheduleRecurrenceDeleteUrl = "{{ url_for("schedule.recurrence.delete.form", {id:':id'}) }}";
|
|
|
|
|
var layoutPreviewUrl = "{{ theme.rootUri() }}preview/layout/preview/:id";
|
|
|
|
|
var scheduleSearchUrl = "{{ url_for("schedule.search") }}";
|
|
|
|
|
var userAgendaViewEnabled = "{{ currentUser.featureEnabled('schedule.agenda') }}";
|
|
|
|
|
|
|
|
|
|
{# Custom translations #}
|
|
|
|
|
var schedulePageTrans = {
|
|
|
|
|
always: "{% trans "Always" %}",
|
|
|
|
|
adjustTimesofTimer: "{% trans "Adjust the times of this timer. To add or remove a day, use the Display Profile." %}",
|
|
|
|
|
daysOfTheWeek: {
|
|
|
|
|
monday: "{% trans "Monday" %}",
|
|
|
|
|
tuesday: "{% trans "Tuesday" %}",
|
|
|
|
|
wednesday: "{% trans "Wednesday" %}",
|
|
|
|
|
thursday: "{% trans "Thursday" %}",
|
|
|
|
|
friday: "{% trans "Friday" %}",
|
|
|
|
|
saturday: "{% trans "Saturday" %}",
|
|
|
|
|
sunday: "{% trans "Sunday" %}",
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
{# Add page source code bundle ( JS ) #}
|
|
|
|
|
<script src="{{ theme.rootUri() }}dist/leaflet.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
|
|
|
|
|
<script src="{{ theme.rootUri() }}dist/pages/schedule-page.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
|
2026-03-23 21:09:27 -04:00
|
|
|
|
|
|
|
|
{# ── FullCalendar v6 (inlined to bypass MIME issues with /custom/ paths) ── #}
|
|
|
|
|
<script nonce="{{ cspNonce }}">
|
|
|
|
|
{% include "fullcalendar-lib.twig" %}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
{# ── OTS FullCalendar integration ── #}
|
2026-02-11 20:47:09 -05:00
|
|
|
<script nonce="{{ cspNonce }}">
|
|
|
|
|
$(function() {
|
2026-03-23 21:09:27 -04:00
|
|
|
// OTS calendar nav arrows drive FullCalendar directly (do NOT proxy to
|
|
|
|
|
// Xibo's data-calendar-nav buttons — those trigger the legacy calendar
|
|
|
|
|
// and cause _loadEvents / options.events errors).
|
2026-02-11 20:47:09 -05:00
|
|
|
$('#ots-cal-prev').on('click', function() {
|
2026-03-23 21:09:27 -04:00
|
|
|
if (otsCalendar) { otsCalendar.prev(); }
|
2026-02-11 20:47:09 -05:00
|
|
|
});
|
|
|
|
|
$('#ots-cal-next').on('click', function() {
|
2026-03-23 21:09:27 -04:00
|
|
|
if (otsCalendar) { otsCalendar.next(); }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ── FullCalendar initialisation ──
|
|
|
|
|
var otsCalendar = null;
|
|
|
|
|
var calendarTabActive = false;
|
|
|
|
|
|
|
|
|
|
// Map Xibo event data to FullCalendar event object
|
|
|
|
|
function mapEvent(item) {
|
|
|
|
|
var title = item.schedule || item.campaign || item.command || item.layout || 'Event #' + item.eventId;
|
|
|
|
|
var cls = 'fc-event-single';
|
|
|
|
|
|
|
|
|
|
// Priority flag overrides everything
|
|
|
|
|
if (item.isPriority && parseInt(item.isPriority, 10) > 0) {
|
|
|
|
|
cls = 'fc-event-priority';
|
|
|
|
|
}
|
|
|
|
|
// Command events
|
|
|
|
|
else if (parseInt(item.eventTypeId, 10) === 2) {
|
|
|
|
|
cls = 'fc-event-command';
|
|
|
|
|
}
|
|
|
|
|
// Interrupt events
|
|
|
|
|
else if (parseInt(item.eventTypeId, 10) === 4) {
|
|
|
|
|
cls = 'fc-event-interrupt';
|
|
|
|
|
}
|
|
|
|
|
// Action events
|
|
|
|
|
else if (parseInt(item.eventTypeId, 10) === 6) {
|
|
|
|
|
cls = 'fc-event-action';
|
|
|
|
|
}
|
|
|
|
|
// "Always" daypart
|
|
|
|
|
else if (item.isAlways && parseInt(item.isAlways, 10) === 1) {
|
|
|
|
|
cls = 'fc-event-always';
|
|
|
|
|
}
|
|
|
|
|
// Geo-aware
|
|
|
|
|
else if (item.isGeoAware && parseInt(item.isGeoAware, 10) === 1) {
|
|
|
|
|
cls = 'fc-event-geo';
|
|
|
|
|
}
|
|
|
|
|
// Recurring
|
|
|
|
|
else if (item.recurrenceType && item.recurrenceType !== '' && item.recurrenceType !== 'null') {
|
|
|
|
|
cls = 'fc-event-recurring';
|
|
|
|
|
}
|
|
|
|
|
// Multi-display vs single
|
|
|
|
|
else if (item.displayGroups && item.displayGroups.length > 1) {
|
|
|
|
|
cls = 'fc-event-multi';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sync events
|
|
|
|
|
if (item.syncGroupId && parseInt(item.syncGroupId, 10) > 0) {
|
|
|
|
|
cls = 'fc-event-sync';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
id: item.eventId,
|
|
|
|
|
title: title,
|
|
|
|
|
start: item.fromDt,
|
|
|
|
|
end: item.toDt,
|
|
|
|
|
allDay: (item.isAlways && parseInt(item.isAlways, 10) === 1),
|
|
|
|
|
className: cls,
|
|
|
|
|
extendedProps: item
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetch events from Xibo schedule.search API
|
|
|
|
|
function fetchEvents(info, successCallback, failureCallback) {
|
|
|
|
|
var filterData = {
|
|
|
|
|
fromDt: info.startStr,
|
|
|
|
|
toDt: info.endStr
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Pull in active filter values
|
|
|
|
|
var $filter = $('#schedule-filter');
|
|
|
|
|
if ($filter.length) {
|
|
|
|
|
$filter.find(':input[name]').each(function() {
|
|
|
|
|
var $el = $(this);
|
|
|
|
|
var name = $el.attr('name');
|
|
|
|
|
var val = $el.val();
|
|
|
|
|
if (val && val !== '' && name !== 'fromDt' && name !== 'toDt') {
|
|
|
|
|
filterData[name] = val;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$.ajax({
|
|
|
|
|
url: scheduleSearchUrl,
|
|
|
|
|
type: 'GET',
|
|
|
|
|
dataType: 'json',
|
|
|
|
|
data: filterData,
|
|
|
|
|
success: function(response) {
|
|
|
|
|
var events = [];
|
|
|
|
|
var rows = response.data || response || [];
|
|
|
|
|
for (var i = 0; i < rows.length; i++) {
|
|
|
|
|
events.push(mapEvent(rows[i]));
|
|
|
|
|
}
|
|
|
|
|
successCallback(events);
|
|
|
|
|
},
|
|
|
|
|
error: function(xhr) {
|
|
|
|
|
console.warn('OTS: FullCalendar event fetch failed', xhr.status);
|
|
|
|
|
failureCallback(xhr);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle event click → open Xibo edit form
|
|
|
|
|
function handleEventClick(info) {
|
|
|
|
|
var ev = info.event;
|
|
|
|
|
var props = ev.extendedProps || {};
|
|
|
|
|
if (props.eventId) {
|
|
|
|
|
var url = $('#CalendarContainer').data('agenda-link');
|
|
|
|
|
if (url) {
|
|
|
|
|
url = url.replace(':id', props.eventId);
|
|
|
|
|
XiboFormRender(url);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialise FullCalendar on first Calendar-tab show
|
|
|
|
|
function initFullCalendar() {
|
|
|
|
|
if (otsCalendar) return;
|
|
|
|
|
var el = document.getElementById('ots-fullcalendar');
|
|
|
|
|
if (!el || typeof FullCalendar === 'undefined') return;
|
|
|
|
|
|
|
|
|
|
otsCalendar = new FullCalendar.Calendar(el, {
|
|
|
|
|
initialView: 'dayGridMonth',
|
|
|
|
|
headerToolbar: {
|
|
|
|
|
left: 'prev,next today',
|
|
|
|
|
center: 'title',
|
|
|
|
|
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
|
|
|
|
|
},
|
|
|
|
|
buttonText: {
|
|
|
|
|
today: '{{ "Today"|trans }}',
|
|
|
|
|
month: '{{ "Month"|trans }}',
|
|
|
|
|
week: '{{ "Week"|trans }}',
|
|
|
|
|
day: '{{ "Day"|trans }}',
|
|
|
|
|
list: '{{ "List"|trans }}'
|
|
|
|
|
},
|
|
|
|
|
firstDay: 1,
|
|
|
|
|
nowIndicator: true,
|
|
|
|
|
navLinks: true,
|
|
|
|
|
editable: false,
|
|
|
|
|
selectable: false,
|
|
|
|
|
eventDisplay: 'block',
|
|
|
|
|
dayMaxEvents: 4,
|
|
|
|
|
height: 'auto',
|
|
|
|
|
events: fetchEvents,
|
|
|
|
|
eventClick: handleEventClick,
|
|
|
|
|
loading: function(isLoading) {
|
|
|
|
|
$('#calendar-progress').toggle(isLoading);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
otsCalendar.render();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Init when Calendar tab is shown
|
|
|
|
|
$('a[data-toggle="tab"][href="#calendar-view"]').on('shown.bs.tab', function() {
|
|
|
|
|
calendarTabActive = true;
|
|
|
|
|
initFullCalendar();
|
|
|
|
|
// Hide the Xibo calendar header (we use FC's built-in toolbar)
|
|
|
|
|
$('.xibo-calendar-header-container').hide();
|
2026-02-11 20:47:09 -05:00
|
|
|
});
|
2026-03-23 21:09:27 -04:00
|
|
|
|
|
|
|
|
// Re-show Xibo header when switching back to Grid; suspend FC interception
|
|
|
|
|
$('a[data-toggle="tab"][href="#grid-view"]').on('shown.bs.tab', function() {
|
|
|
|
|
$('.xibo-calendar-header-container').show();
|
|
|
|
|
calendarTabActive = false;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Intercept Xibo's data-calendar-nav buttons when calendar tab is active
|
|
|
|
|
// and drive FullCalendar directly, preventing the legacy calendar from being invoked.
|
|
|
|
|
$(document).on('click', '[data-calendar-nav]', function(e) {
|
|
|
|
|
if (!calendarTabActive || !otsCalendar) return; // Grid tab active — let Xibo handle it
|
|
|
|
|
e.stopImmediatePropagation();
|
|
|
|
|
var nav = $(this).data('calendar-nav');
|
|
|
|
|
if (nav === 'prev') { otsCalendar.prev(); }
|
|
|
|
|
else if (nav === 'next') { otsCalendar.next(); }
|
|
|
|
|
else if (nav === 'today') { otsCalendar.today(); }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Refetch events when filter changes
|
|
|
|
|
$('#schedule-filter').on('change', ':input', function() {
|
|
|
|
|
if (otsCalendar) {
|
|
|
|
|
otsCalendar.refetchEvents();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// If Calendar tab is active by default (URL hash), init immediately
|
|
|
|
|
if (window.location.hash === '#calendar-view' || $('#calendar-tab').hasClass('active')) {
|
|
|
|
|
calendarTabActive = true;
|
|
|
|
|
setTimeout(initFullCalendar, 100);
|
|
|
|
|
}
|
2026-02-11 20:47:09 -05:00
|
|
|
});
|
|
|
|
|
</script>
|
2026-02-04 15:26:44 -05:00
|
|
|
{% endblock %}
|