mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-18 23:42:17 +00:00
Stundenplan & Calendar
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
if (! defined('BASEPATH')) exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class Stundenplan extends Auth_Controller
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct([
|
||||
'index' => ['student/anrechnung_beantragen:r','user:r'] // TODO(chris): permissions?
|
||||
]);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
// Public methods
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->load->view('Cis/Stundenplan');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
if (! defined('BASEPATH')) exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class Stundenplan extends Auth_Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Object initialization
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct([
|
||||
'index' => ['student/anrechnung_beantragen:r','user:r'], // TODO(chris): permissions?
|
||||
'Reservierungen' => ['student/anrechnung_beantragen:r','user:r'], // TODO(chris): permissions?
|
||||
'Stunden' => ['student/anrechnung_beantragen:r','user:r'] // TODO(chris): permissions?
|
||||
]);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Public methods
|
||||
|
||||
/**
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->load->model('ressource/Stundenplan_model', 'StundenplanModel');
|
||||
|
||||
$result = $this->StundenplanModel->loadForUid(get_uid());
|
||||
|
||||
if (isError($result))
|
||||
return $this->outputJsonError(getError($result));
|
||||
|
||||
$this->outputJsonSuccess(getData($result));
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public function Reservierungen()
|
||||
{
|
||||
$this->load->model('ressource/Reservierung_model', 'ReservierungModel');
|
||||
|
||||
$result = $this->ReservierungModel->loadForUid(get_uid());
|
||||
|
||||
if (isError($result))
|
||||
return $this->outputJsonError(getError($result));
|
||||
|
||||
$this->outputJsonSuccess(getData($result));
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public function Stunden()
|
||||
{
|
||||
$this->load->model('ressource/Stunde_model', 'StundeModel');
|
||||
|
||||
$result = $this->StundeModel->load();
|
||||
|
||||
if (isError($result))
|
||||
return $this->outputJsonError(getError($result));
|
||||
|
||||
$this->outputJsonSuccess(getData($result));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,4 +11,25 @@ class Reservierung_model extends DB_Model
|
||||
$this->dbTable = 'campus.tbl_reservierung';
|
||||
$this->pk = 'reservierung_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $uid
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
public function loadForUid($uid)
|
||||
{
|
||||
$this->addSelect('r.*');
|
||||
$this->db->join('public.tbl_benutzergruppe bg', 'r.gruppe_kurzbz=bg.gruppe_kurzbz AND bg.uid=?', 'LEFT', false);
|
||||
$this->addJoin('public.tbl_studiensemester ss1', 'bg.studiensemester_kurzbz=ss1.studiensemester_kurzbz AND ss1.start<=r.datum AND ss1.ende>=r.datum', 'LEFT');
|
||||
$this->db->join('public.tbl_studentlehrverband slv', "r.studiengang_kz=slv.studiengang_kz AND slv.student_uid=? AND (slv.semester=r.semester OR r.semester IS NULL) AND (slv.verband=r.verband OR r.verband IS NULL OR r.verband='' OR r.verband='0') AND (slv.gruppe=r.gruppe OR r.gruppe IS NULL OR r.gruppe='' OR r.gruppe='0') AND r.gruppe_kurzbz IS NULL", 'LEFT', false);
|
||||
$this->addJoin('public.tbl_studiensemester ss2', 'slv.studiensemester_kurzbz=ss2.studiensemester_kurzbz AND ss2.start<=r.datum AND ss2.ende>=r.datum', 'LEFT');
|
||||
$this->db->or_where('ss1.studiensemester_kurzbz IS NOT NULL', null, false);
|
||||
$this->db->or_where('ss2.studiensemester_kurzbz IS NOT NULL', null, false);
|
||||
|
||||
$query = $this->db->get_compiled_select('campus.vw_reservierung r');
|
||||
|
||||
return $this->execQuery($query, [$uid, $uid]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,4 +11,25 @@ class Stundenplan_model extends DB_Model
|
||||
$this->dbTable = 'lehre.tbl_stundenplan';
|
||||
$this->pk = 'stundenplan_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uid
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
public function loadForUid($uid)
|
||||
{
|
||||
$this->addSelect('sp.*');
|
||||
$this->db->join('public.tbl_benutzergruppe bg', 'sp.gruppe_kurzbz=bg.gruppe_kurzbz AND bg.uid=?', 'LEFT', false);
|
||||
$this->addJoin('public.tbl_studiensemester ss1', 'bg.studiensemester_kurzbz=ss1.studiensemester_kurzbz AND ss1.start<=sp.datum AND ss1.ende>=sp.datum', 'LEFT');
|
||||
$this->db->join('public.tbl_studentlehrverband slv', "sp.studiengang_kz=slv.studiengang_kz AND slv.student_uid=? AND (slv.semester=sp.semester OR sp.semester IS NULL) AND (slv.verband=sp.verband OR sp.verband IS NULL OR sp.verband='' OR sp.verband='0') AND (slv.gruppe=sp.gruppe OR sp.gruppe IS NULL OR sp.gruppe='' OR sp.gruppe='0') AND sp.gruppe_kurzbz IS NULL", 'LEFT', false);
|
||||
$this->addJoin('public.tbl_studiensemester ss2', 'slv.studiensemester_kurzbz=ss2.studiensemester_kurzbz AND ss2.start<=sp.datum AND ss2.ende>=sp.datum', 'LEFT');
|
||||
$this->db->or_where('ss1.studiensemester_kurzbz IS NOT NULL', null, false);
|
||||
$this->db->or_where('ss2.studiensemester_kurzbz IS NOT NULL', null, false);
|
||||
|
||||
$query = $this->db->get_compiled_select('lehre.vw_stundenplan sp');
|
||||
|
||||
return $this->execQuery($query, [$uid, $uid]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
$includesArray = array(
|
||||
'customJSModules' => ['public/js/apps/Cis/Stundenplan.js'],
|
||||
'customCSSs' => ['public/css/components/calendar.css']
|
||||
);
|
||||
|
||||
$this->load->view('templates/CISHTML-Header', $includesArray);
|
||||
?>
|
||||
|
||||
<div id="content">
|
||||
<h2>Stundenplan</h2>
|
||||
<hr>
|
||||
<fhc-calendar :events="events" initial-mode="week" show-weeks></fhc-calendar>
|
||||
</div>
|
||||
|
||||
<?php $this->load->view('templates/CISHTML-Footer', $includesArray); ?>
|
||||
@@ -0,0 +1,160 @@
|
||||
.fhc-calendar-week-page-header {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
.fhc-calendar-months .col-4,
|
||||
.fhc-calendar-years .col-4 {
|
||||
padding: 0.1875em 0;
|
||||
}
|
||||
.show-weeks .fhc-calendar-months .col-4,
|
||||
.show-weeks .fhc-calendar-years .col-4 {
|
||||
padding: 0.1875em 0.25em;
|
||||
}
|
||||
.fhc-calendar-months .col-4 button,
|
||||
.fhc-calendar-years .col-4 button {
|
||||
aspect-ratio: 28/18;
|
||||
}
|
||||
|
||||
.fhc-calendar-month-page {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
grid-template-rows: 1.5em repeat(6, 1fr);
|
||||
}
|
||||
.fhc-calendar-month-page.show-weeks {
|
||||
grid-template-columns: 1.5em repeat(7, 1fr);
|
||||
grid-template-rows: 1.5em repeat(6, 1fr);
|
||||
}
|
||||
|
||||
.fhc-calendar-month-page-weekday,
|
||||
.fhc-calendar-month-page-day {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.fhc-calendar-week .carousel-item {
|
||||
padding: 0.75em;
|
||||
}
|
||||
|
||||
.fhc-calendar-week-page {
|
||||
aspect-ratio: 7/6;
|
||||
min-height: 0;
|
||||
}
|
||||
.fhc-calendar-week-page > div {
|
||||
transform: translate(-0.75em, -0.75em);
|
||||
width: calc(100% + 1.5em);
|
||||
height: calc(100% + 1.5em);
|
||||
}
|
||||
.fhc-calendar-week-page > div > div {
|
||||
padding-left: 3em;
|
||||
}
|
||||
.fhc-calendar-week-page .events {
|
||||
display: grid;
|
||||
grid-template-columns: 3em repeat(7, 1fr);
|
||||
margin-left: -3em;
|
||||
min-height: 266.6666666667%;
|
||||
background-image: linear-gradient(#f8f9fa calc(100% / 24 - .5px), #dee2e6 calc(100% / 24 - .5px), #dee2e6 calc(100% / 24 + .5px), #f8f9fa calc(100% / 24 + .5px), #f8f9fa calc(200% / 24 - .5px), #dee2e6 calc(200% / 24 - .5px), #dee2e6 calc(200% / 24 + .5px), #f8f9fa calc(200% / 24 + .5px), #f8f9fa calc(300% / 24 - .5px), #dee2e6 calc(300% / 24 - .5px), #dee2e6 calc(300% / 24 + .5px), #f8f9fa calc(300% / 24 + .5px), #f8f9fa calc(400% / 24 - .5px), #dee2e6 calc(400% / 24 - .5px), #dee2e6 calc(400% / 24 + .5px), #f8f9fa calc(400% / 24 + .5px), #f8f9fa calc(500% / 24 - .5px), #dee2e6 calc(500% / 24 - .5px), #dee2e6 calc(500% / 24 + .5px), #f8f9fa calc(500% / 24 + .5px), #f8f9fa calc(600% / 24 - .5px), #dee2e6 calc(600% / 24 - .5px), #dee2e6 calc(600% / 24 + .5px), #f8f9fa calc(600% / 24 + .5px), #f8f9fa calc(700% / 24 - .5px), #dee2e6 calc(700% / 24 - .5px), #dee2e6 calc(700% / 24 + .5px), #f8f9fa calc(700% / 24 + .5px), #f8f9fa calc(800% / 24 - .5px), #dee2e6 calc(800% / 24 - .5px), #dee2e6 calc(800% / 24 + .5px), #fff calc(800% / 24 + .5px), #fff calc(900% / 24 - .5px), #dee2e6 calc(900% / 24 - .5px), #dee2e6 calc(900% / 24 + .5px), #fff calc(900% / 24 + .5px), #fff calc(1000% / 24 - .5px), #dee2e6 calc(1000% / 24 - .5px), #dee2e6 calc(1000% / 24 + .5px), #fff calc(1000% / 24 + .5px), #fff calc(1100% / 24 - .5px), #dee2e6 calc(1100% / 24 - .5px), #dee2e6 calc(1100% / 24 + .5px), #fff calc(1100% / 24 + .5px), #fff calc(1200% / 24 - .5px), #dee2e6 calc(1200% / 24 - .5px), #dee2e6 calc(1200% / 24 + .5px), #fff calc(1200% / 24 + .5px), #fff calc(1300% / 24 - .5px), #dee2e6 calc(1300% / 24 - .5px), #dee2e6 calc(1300% / 24 + .5px), #fff calc(1300% / 24 + .5px), #fff calc(1400% / 24 - .5px), #dee2e6 calc(1400% / 24 - .5px), #dee2e6 calc(1400% / 24 + .5px), #fff calc(1400% / 24 + .5px), #fff calc(1500% / 24 - .5px), #dee2e6 calc(1500% / 24 - .5px), #dee2e6 calc(1500% / 24 + .5px), #fff calc(1500% / 24 + .5px), #fff calc(1600% / 24 - .5px), #dee2e6 calc(1600% / 24 - .5px), #dee2e6 calc(1600% / 24 + .5px), #fff calc(1600% / 24 + .5px), #fff calc(1700% / 24 - .5px), #dee2e6 calc(1700% / 24 - .5px), #dee2e6 calc(1700% / 24 + .5px), #f8f9fa calc(1700% / 24 + .5px), #f8f9fa calc(1800% / 24 - .5px), #dee2e6 calc(1800% / 24 - .5px), #dee2e6 calc(1800% / 24 + .5px), #f8f9fa calc(1800% / 24 + .5px), #f8f9fa calc(1900% / 24 - .5px), #dee2e6 calc(1900% / 24 - .5px), #dee2e6 calc(1900% / 24 + .5px), #f8f9fa calc(1900% / 24 + .5px), #f8f9fa calc(2000% / 24 - .5px), #dee2e6 calc(2000% / 24 - .5px), #dee2e6 calc(2000% / 24 + .5px), #f8f9fa calc(2000% / 24 + .5px), #f8f9fa calc(2100% / 24 - .5px), #dee2e6 calc(2100% / 24 - .5px), #dee2e6 calc(2100% / 24 + .5px), #f8f9fa calc(2100% / 24 + .5px), #f8f9fa calc(2200% / 24 - .5px), #dee2e6 calc(2200% / 24 - .5px), #dee2e6 calc(2200% / 24 + .5px), #f8f9fa calc(2200% / 24 + .5px), #f8f9fa calc(2300% / 24 - .5px), #dee2e6 calc(2300% / 24 - .5px), #dee2e6 calc(2300% / 24 + .5px), #f8f9fa calc(2300% / 24 + .5px));
|
||||
}
|
||||
.fhc-calendar-week-page .events .day {
|
||||
gap: 1px;
|
||||
}
|
||||
.fhc-calendar-week-page .events .hours, .fhc-calendar-week-page .events .day {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.fhc-calendar-lg .fhc-calendar-month-page-day,
|
||||
.fhc-calendar-lg .fhc-calendar-month-page-weekday,
|
||||
.fhc-calendar-md .fhc-calendar-month-page-day,
|
||||
.fhc-calendar-md .fhc-calendar-month-page-weekday {
|
||||
border: solid 1px #dee2e6;
|
||||
}
|
||||
.fhc-calendar-lg .fhc-calendar-month-page-day,
|
||||
.fhc-calendar-md .fhc-calendar-month-page-day {
|
||||
aspect-ratio: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.fhc-calendar-lg .fhc-calendar-month-page-day.active,
|
||||
.fhc-calendar-md .fhc-calendar-month-page-day.active {
|
||||
border-color: var(--bs-secondary);
|
||||
}
|
||||
.fhc-calendar-lg .fhc-calendar-month-page-day .events,
|
||||
.fhc-calendar-md .fhc-calendar-month-page-day .events {
|
||||
display: block;
|
||||
overflow: scroll;
|
||||
font-size: 0.7em;
|
||||
}
|
||||
.fhc-calendar-lg .fhc-calendar-month-page-day .events span,
|
||||
.fhc-calendar-md .fhc-calendar-month-page-day .events span {
|
||||
display: block;
|
||||
margin: 0.2em;
|
||||
padding: 0.1em 0.4em;
|
||||
border-radius: 0.1em;
|
||||
}
|
||||
.fhc-calendar-lg .fhc-calendar-years .col-4,
|
||||
.fhc-calendar-md .fhc-calendar-years .col-4 {
|
||||
padding: 0.09375em 0;
|
||||
}
|
||||
.show-weeks .fhc-calendar-lg .fhc-calendar-years .col-4,
|
||||
.show-weeks .fhc-calendar-md .fhc-calendar-years .col-4 {
|
||||
padding: 0.09375em 0.25em;
|
||||
}
|
||||
.fhc-calendar-lg .fhc-calendar-years .col-4 button,
|
||||
.fhc-calendar-md .fhc-calendar-years .col-4 button {
|
||||
aspect-ratio: 28/9;
|
||||
}
|
||||
|
||||
.fhc-calendar-xs {
|
||||
font-size: 0.6em;
|
||||
}
|
||||
|
||||
.fhc-calendar-sm .fhc-calendar-month-page {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.fhc-calendar-sm .fhc-calendar-month-page-weekday,
|
||||
.fhc-calendar-xs .fhc-calendar-month-page-weekday {
|
||||
font-style: italic;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
}
|
||||
.fhc-calendar-sm .fhc-calendar-month-page-day,
|
||||
.fhc-calendar-xs .fhc-calendar-month-page-day {
|
||||
position: relative;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
.fhc-calendar-sm .fhc-calendar-month-page-day.active .no,
|
||||
.fhc-calendar-xs .fhc-calendar-month-page-day.active .no {
|
||||
background-color: rgba(var(--bs-info-rgb), 0.25);
|
||||
border-radius: 50%;
|
||||
font-weight: bold;
|
||||
}
|
||||
.fhc-calendar-sm .fhc-calendar-month-page-day:not(.active):hover .no,
|
||||
.fhc-calendar-xs .fhc-calendar-month-page-day:not(.active):hover .no {
|
||||
background-color: rgba(var(--bs-secondary-rgb), 0.25);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.fhc-calendar-sm .fhc-calendar-month-page-day .no,
|
||||
.fhc-calendar-xs .fhc-calendar-month-page-day .no {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
margin: 10%;
|
||||
}
|
||||
.fhc-calendar-sm .fhc-calendar-month-page-day .events,
|
||||
.fhc-calendar-xs .fhc-calendar-month-page-day .events {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 10%;
|
||||
width: 80%;
|
||||
height: 10%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
.fhc-calendar-sm .fhc-calendar-month-page-day .events span,
|
||||
.fhc-calendar-xs .fhc-calendar-month-page-day .events span {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=calendar.css.map */
|
||||
@@ -0,0 +1,53 @@
|
||||
import FhcCalendar from "../../components/Calendar/Calendar.js";
|
||||
|
||||
const app = Vue.createApp({
|
||||
components: {
|
||||
FhcCalendar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
stunden: [],
|
||||
events: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
axios.get(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/components/Cis/Stundenplan/Stunden').then(res => {
|
||||
res.data.retval.forEach(std => {
|
||||
this.stunden[std.stunde] = std; // TODO(chris): geht besser
|
||||
});
|
||||
axios.get(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/components/Cis/Stundenplan').then(res => {
|
||||
let events;
|
||||
if (res.data.retval && res.data.retval.forEach) {
|
||||
res.data.retval.forEach((el, i) => {
|
||||
el.id = i;
|
||||
el.color = '#' + (el.farbe || 'CCCCCC');
|
||||
el.start = new Date(el.datum + ' ' + this.stunden[el.stunde].beginn);
|
||||
el.end = new Date(el.datum + ' ' + this.stunden[el.stunde].ende);
|
||||
el.title = el.lehrfach;
|
||||
if (el.lehrform)
|
||||
el.title += '-' + el.lehrform;
|
||||
});
|
||||
events = res.data.retval;
|
||||
}
|
||||
// TODO(chris): do we need that
|
||||
axios.get(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/components/Cis/Stundenplan/Reservierungen').then(res => {
|
||||
if (res.data.retval && res.data.retval.forEach) {
|
||||
res.data.retval.forEach((el, i) => {
|
||||
el.id = i + events.length;
|
||||
el.color = '#CCCCCC';
|
||||
el.start = new Date(el.datum + ' ' + this.stunden[el.stunde].beginn);
|
||||
el.end = new Date(el.datum + ' ' + this.stunden[el.stunde].ende);
|
||||
el.title = el.lehrfach;
|
||||
if (el.lehrform)
|
||||
el.title += '-' + el.lehrform;
|
||||
});
|
||||
events = [...events, ...res.data.retval];
|
||||
}
|
||||
this.events = events;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
app.config.unwrapInjectedRef = true;
|
||||
app.mount('#content');
|
||||
@@ -0,0 +1,17 @@
|
||||
import CalendarHeader from './Header';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CalendarHeader
|
||||
},
|
||||
inject: [
|
||||
'date',
|
||||
'focusDate',
|
||||
'size'
|
||||
],
|
||||
emits: [
|
||||
'update:mode',
|
||||
'change:range',
|
||||
'input'
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import CalendarMonth from './Month.js';
|
||||
import CalendarMonths from './Months.js';
|
||||
import CalendarYears from './Years.js';
|
||||
import CalendarWeek from './Week.js';
|
||||
import CalendarWeeks from './Weeks.js';
|
||||
import CalendarMinimized from './Minimized.js';
|
||||
import CalendarDate from '../../composables/CalendarDate.js';
|
||||
|
||||
// TODO(chris): week/month toggle
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CalendarMonth,
|
||||
CalendarMonths,
|
||||
CalendarYears,
|
||||
CalendarWeek,
|
||||
CalendarWeeks,
|
||||
CalendarMinimized
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
date: this.date,
|
||||
focusDate: this.focusDate,
|
||||
size: Vue.computed({ get: () => this.size, set: v => this.size = v}),
|
||||
events: Vue.computed(() => this.eventsPerDay),
|
||||
minimized: Vue.computed({ get: () => this.minimized, set: v => this.$emit('update:minimized',v) }),
|
||||
showWeeks: this.showWeeks,
|
||||
noMonthView: this.noMonthView,
|
||||
noWeekView: this.noWeekView,
|
||||
eventsAreNull: Vue.computed(() => this.events === null)
|
||||
};
|
||||
},
|
||||
props: {
|
||||
events: Array,
|
||||
showWeeks: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
initialMode: {
|
||||
type: String,
|
||||
default: 'month'
|
||||
},
|
||||
minimized: Boolean,
|
||||
noWeekView: Boolean,
|
||||
noMonthView: Boolean
|
||||
},
|
||||
emits: [
|
||||
'select:day',
|
||||
'select:event',
|
||||
'change:range',
|
||||
'update:minimized'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
header: '',
|
||||
prevMode: null,
|
||||
currMode: null,
|
||||
date: new CalendarDate(),
|
||||
focusDate: new CalendarDate(),
|
||||
size: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sizeClass() {
|
||||
return 'fhc-calendar-' + ['xs','sm','md','lg'][this.size];
|
||||
},
|
||||
mode: {
|
||||
get() { return this.minimized ? 'minimized' : this.currMode; },
|
||||
set(v) {
|
||||
if (!v && this.prevMode) {
|
||||
this.currMode = this.prevMode;
|
||||
this.prevMode = null;
|
||||
} else {
|
||||
this.prevMode = this.currMode;
|
||||
this.currMode = v;
|
||||
}
|
||||
}
|
||||
},
|
||||
eventsPerDay() {
|
||||
if (!this.events)
|
||||
return {};
|
||||
return this.events.reduce((result, event) => {
|
||||
let days = Math.ceil((event.end - event.start) / 86400000) || 1;
|
||||
while (days-- > 0) {
|
||||
let day = (new Date(event.start.getFullYear(), event.start.getMonth(), event.start.getDate() + days)).toDateString();
|
||||
if (!result[day])
|
||||
result[day] = [];
|
||||
result[day].push(event);
|
||||
}
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleInput(day) {
|
||||
this.$emit(day[0], day[1]);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const allowedInitialModes = ['years'];
|
||||
if (!this.noWeekView)
|
||||
allowedInitialModes.push('week');
|
||||
if (!this.noMonthView)
|
||||
allowedInitialModes.push('month');
|
||||
this.mode = allowedInitialModes[allowedInitialModes.indexOf(this.initialMode)] || allowedInitialModes.pop();
|
||||
this.date.set(new Date(2023,0,1));
|
||||
this.focusDate.set(this.date);
|
||||
},
|
||||
mounted() {
|
||||
if (this.$refs.container) {
|
||||
new ResizeObserver(entries => {
|
||||
for (const entry of entries) {
|
||||
let w = entry.contentBoxSize ? entry.contentBoxSize[0].inlineSize : entry.contentRect.width;
|
||||
// TODO(chris): rework sizing
|
||||
if (w > 600)
|
||||
this.size = 3;
|
||||
else if (w > 350)
|
||||
this.size = 2;
|
||||
else if (w > 250)
|
||||
this.size = 1;
|
||||
else
|
||||
this.size = 0;
|
||||
}
|
||||
}).observe(this.$refs.container);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div ref="container" class="fhc-calendar card" :class="sizeClass">
|
||||
<component :is="'calendar-' + mode" @update:mode="mode=$event" @change:range="$emit('change:range',$event)" @input="handleInput" />
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
export default {
|
||||
inject: [
|
||||
'eventsAreNull',
|
||||
'size'
|
||||
],
|
||||
props: {
|
||||
title: String
|
||||
},
|
||||
emits: [
|
||||
'prev',
|
||||
'next',
|
||||
'click'
|
||||
],
|
||||
template: `
|
||||
<div class="calendar-header card-header btn-group w-100">
|
||||
<button class="btn btn-outline-secondary border-0 flex-grow-0" :class="{'btn-sm': !this.size}" @click="$emit('prev')"><i class="fa fa-chevron-left"></i></button>
|
||||
<button class="btn btn-link link-secondary text-decoration-none" :class="{'btn-sm': !this.size}" @click="$emit('click')">
|
||||
{{ title }}
|
||||
<i v-if="eventsAreNull" class="fa fa-spinner fa-pulse"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary border-0 flex-grow-0" :class="{'btn-sm': !this.size}" @click="$emit('next')"><i class="fa fa-chevron-right"></i></button>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import CalendarAbstract from './Abstract.js';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
CalendarAbstract
|
||||
],
|
||||
inject: [
|
||||
'size',
|
||||
'minimized',
|
||||
'date'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
start: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
maximize() {
|
||||
// NOTE(chris): set "app.config.unwrapInjectedRef = true" for this to work
|
||||
this.minimized = false;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-minimized">
|
||||
<div class="card-header d-grid">
|
||||
<button class="btn btn-link link-secondary text-decoration-none" @click="maximize">{{ date.format({dateStyle: ['long','full','full','full'][this.size]}) }}</button>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import CalendarAbstract from './Abstract.js';
|
||||
import CalendarPane from './Pane.js';
|
||||
import CalendarMonthPage from './Month/Page.js';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
CalendarAbstract
|
||||
],
|
||||
components: {
|
||||
CalendarMonthPage,
|
||||
CalendarPane
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
syncOnNextChange: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return this.focusDate.format({month: ['short','long','long','long'][this.size], year: 'numeric'});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
paneChanged(dir) {
|
||||
if (this.syncOnNextChange) {
|
||||
this.syncOnNextChange = false;
|
||||
this.focusDate.set(this.date);
|
||||
} else {
|
||||
this.focusDate.m += dir;
|
||||
}
|
||||
this.$emit('change:range', {
|
||||
start: new Date(this.focusDate.y, this.focusDate.m, 1),
|
||||
end: new Date(this.focusDate.y, this.focusDate.m+1, 0)
|
||||
});
|
||||
},
|
||||
prev() {
|
||||
this.$refs.pane.prev();
|
||||
},
|
||||
next() {
|
||||
this.$refs.pane.next();
|
||||
},
|
||||
selectDay(day) {
|
||||
let m = day.getMonth();
|
||||
if (this.focusDate.m != m) {
|
||||
this.syncOnNextChange = true;
|
||||
if (this.focusDate.m-1 == m || (m == 11 && !this.focusDate.m))
|
||||
this.$refs.pane.prev();
|
||||
else
|
||||
this.$refs.pane.next();
|
||||
} else {
|
||||
this.focusDate.set(this.date);
|
||||
}
|
||||
this.$emit('input', ['select:day',day])
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$emit('change:range', {
|
||||
start: new Date(this.focusDate.y, this.focusDate.m, 1),
|
||||
end: new Date(this.focusDate.y, this.focusDate.m+1, 0)
|
||||
});
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-month">
|
||||
<calendar-header :title="title" @prev="prev" @next="next" @click="$emit('update:mode', 'months')" />
|
||||
<calendar-pane ref="pane" v-slot="slot" @slid="paneChanged">
|
||||
<calendar-month-page :year="focusDate.y" :month="focusDate.m+slot.offset" @update:mode="$emit('update:mode', $event)" @page:back="prev" @page:forward="next" @input="selectDay" />
|
||||
</calendar-pane>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import CalendarDate from '../../../composables/CalendarDate.js';
|
||||
|
||||
export default {
|
||||
inject: [
|
||||
'date',
|
||||
'focusDate',
|
||||
'size',
|
||||
'events',
|
||||
'showWeeks',
|
||||
'noWeekView'
|
||||
],
|
||||
props: {
|
||||
year: Number,
|
||||
month: Number
|
||||
},
|
||||
emits: [
|
||||
'update:mode',
|
||||
'page:back',
|
||||
'page:forward',
|
||||
'input'
|
||||
],
|
||||
computed: {
|
||||
weeks() {
|
||||
let firstDayOfMonth = new CalendarDate(this.year, this.month, 1);
|
||||
let startDay = firstDayOfMonth.firstDayOfCalendarMonth;
|
||||
let endDay = firstDayOfMonth.lastDayOfCalendarMonth;
|
||||
|
||||
let res = [];
|
||||
let week = {no:0,y:0,days:[]};
|
||||
while (startDay <= endDay) {
|
||||
week.days.push(new Date(startDay));
|
||||
if (week.days.length == 7) {
|
||||
let d = new CalendarDate(week.days[res.length ? 0 : 6]);
|
||||
week.no = d.w;
|
||||
week.y = d.y;
|
||||
res.push(week);
|
||||
week = {no:0,y:0,days:[]};
|
||||
}
|
||||
startDay.setDate(startDay.getDate() + 1);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectDay(day) {
|
||||
this.date.set(day);
|
||||
this.$emit('input', day);
|
||||
},
|
||||
changeToWeek(week) {
|
||||
if (!this.noWeekView) {
|
||||
if (!this.focusDate.isInWeek(week.no, week.y))
|
||||
this.focusDate.set(week.days[0]);
|
||||
this.$emit('update:mode', 'week');
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-month-page" :class="{'show-weeks': showWeeks}">
|
||||
<div v-if="showWeeks" class="bg-light fw-bold border-top border-bottom text-center"></div>
|
||||
<div v-for="day in weeks[0].days" :key="day" class="bg-light fw-bold border-top border-bottom text-center">
|
||||
{{day.toLocaleString(undefined, {weekday: size < 1 ? 'narrow' : (size < 3 ? 'short' : 'long')})}}
|
||||
</div>
|
||||
<template v-for="week in weeks" :key="week.no">
|
||||
<a href="#" v-if="showWeeks" class="fhc-calendar-month-page-weekday text-decoration-none text-end opacity-25" @click.prevent="changeToWeek(week)">{{week.no}}</a>
|
||||
<a href="#" v-for="day in week.days" :key="day" class="fhc-calendar-month-page-day text-decoration-none overflow-hidden" :class="{active:date.compare(day),'opacity-50':day.getMonth() != month}" @click.prevent="selectDay(day)">
|
||||
<span class="no">{{day.getDate()}}</span>
|
||||
<span v-if="events[day.toDateString()] && events[day.toDateString()].length" class="events">
|
||||
<span v-for="event in events[day.toDateString()]" :key="event.id" style="color:white" :style="{'background-color': event.color}">
|
||||
{{event.title}}
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import CalendarAbstract from './Abstract.js';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
CalendarAbstract
|
||||
],
|
||||
inject: [
|
||||
'size'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
monthIndices: [...Array(12).keys()]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return this.focusDate.format({year: 'numeric'});
|
||||
},
|
||||
months() {
|
||||
return this.monthIndices.map(i => (new Date(0, i, 1)).toLocaleString(undefined, {month: this.size < 2 ? 'short' : 'long'}));
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-months">
|
||||
<calendar-header :title="title" @prev="focusDate.y--" @next="focusDate.y++" @click="$emit('update:mode', 'years')" />
|
||||
<div class="d-flex flex-wrap">
|
||||
<div v-for="(month, key) in months" :key="key" class="d-grid col-4">
|
||||
<button @click="focusDate.m = key; $emit('update:mode', 'month')" class="btn btn-outline-secondary" :class="{'border-0': key != focusDate.m}">
|
||||
{{month}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
export default {
|
||||
emits: [
|
||||
'slid'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
carousel: null,
|
||||
queue: 0,
|
||||
offset: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
offsets() {
|
||||
return [...Array(3).keys()].map(i => (3+i-this.offset)%3-1);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
prev() {
|
||||
if (!this.queue--)
|
||||
this.carousel.prev();
|
||||
},
|
||||
next() {
|
||||
if (!this.queue++)
|
||||
this.carousel.next();
|
||||
},
|
||||
slid(evt) {
|
||||
let dir = evt.direction == 'left' ? 1 : -1;
|
||||
this.queue -= dir;
|
||||
this.$emit('slid', dir);
|
||||
this.offset = (3+this.offset+dir)%3;
|
||||
if (this.queue) {
|
||||
if (this.queue > 0)
|
||||
this.carousel.next();
|
||||
else
|
||||
this.carousel.prev();
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.$refs.carousel) {
|
||||
this.$refs.carousel.children[0].children[1].classList.add('active');
|
||||
this.carousel = new window.bootstrap.Carousel(this.$refs.carousel, {
|
||||
interval: false
|
||||
});
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div ref="carousel" class="calendar-pane carousel slide" @[\`slid.bs.carousel\`]="slid" :data-queue="queue">
|
||||
<div class="carousel-inner">
|
||||
<div v-for="i in [...Array(3).keys()]" :key="i" class="carousel-item">
|
||||
<slot :index="i" :offset="offsets[i]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import CalendarAbstract from './Abstract.js';
|
||||
import CalendarPane from './Pane.js';
|
||||
import CalendarWeekPage from './Week/Page.js';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
CalendarAbstract
|
||||
],
|
||||
components: {
|
||||
CalendarWeekPage,
|
||||
CalendarPane
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return this.focusDate.format({year: 'numeric'}) + ' KW ' + this.focusDate.w;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
paneChanged(dir) {
|
||||
this.focusDate.d += dir * 7;
|
||||
this.emitRangeChanged();
|
||||
},
|
||||
emitRangeChanged() {
|
||||
let start = this.focusDate.firstDayOfWeek;
|
||||
let end = this.focusDate.lastDayOfWeek;
|
||||
this.$emit('change:range', { start, end });
|
||||
},
|
||||
prev() {
|
||||
this.$refs.pane.prev();
|
||||
},
|
||||
next() {
|
||||
this.$refs.pane.next();
|
||||
},
|
||||
selectEvent(event) {
|
||||
this.$emit('input', ['select:event',event]);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.emitRangeChanged();
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-week">
|
||||
<calendar-header :title="title" @prev="prev" @next="next" @click="$emit('update:mode', 'weeks')" />
|
||||
<calendar-pane ref="pane" v-slot="slot" @slid="paneChanged">
|
||||
<calendar-week-page :year="focusDate.y" :week="focusDate.w+slot.offset" @update:mode="$emit('update:mode', $event)" @page:back="prev" @page:forward="next" @input="selectEvent" />
|
||||
</calendar-pane>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import CalendarDate from '../../../composables/CalendarDate.js';
|
||||
|
||||
function ggt(m,n) { return n==0 ? m : ggt(n, m%n); }
|
||||
function kgv(m,n) { return (m*n) / ggt(m,n); }
|
||||
|
||||
export default {
|
||||
inject: [
|
||||
'date',
|
||||
'focusDate',
|
||||
'size',
|
||||
'events',
|
||||
'noMonthView'
|
||||
],
|
||||
props: {
|
||||
year: Number,
|
||||
week: Number
|
||||
},
|
||||
emits: [
|
||||
'update:mode',
|
||||
'page:back',
|
||||
'page:forward',
|
||||
'input'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
hours: [...Array(24).keys()]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
days() {
|
||||
let tmpDate = new CalendarDate(this.year, 1, 1); // NOTE(chris): somewhere in the middle of the year
|
||||
tmpDate.w = this.week;
|
||||
let startDay = tmpDate.firstDayOfWeek;
|
||||
let result = [];
|
||||
for (let i = 0; i < 7; i++) {
|
||||
result.push(new Date(startDay.getFullYear(), startDay.getMonth(), startDay.getDate() + i));
|
||||
}
|
||||
return result;
|
||||
},
|
||||
eventsPerDayAndHour() {
|
||||
const res = {};
|
||||
this.days.forEach(day => {
|
||||
let key = day.toDateString();
|
||||
|
||||
let nextDay = new Date(day);
|
||||
nextDay.setDate(nextDay.getDate()+1);
|
||||
nextDay.setMilliseconds(nextDay.getMilliseconds()-1);
|
||||
let d = {events:[],lanes:1};
|
||||
if (this.events[key]) {
|
||||
this.events[key].forEach(evt => {
|
||||
let event = {orig:evt,lane:1,maxLane:1,start: evt.start < day ? day : evt.start, end: evt.end > nextDay ? nextDay : evt.end,shared:[],setSharedMaxRecursive(doneItems) {
|
||||
this.maxLane = Math.max(doneItems[0].maxLane, this.maxLane);
|
||||
doneItems.push(this);
|
||||
this.shared.filter(other => !doneItems.includes(other)).forEach(i => i.setSharedMaxRecursive(doneItems));
|
||||
}};
|
||||
event.shared = d.events.filter(other => other.start < event.end && other.end > event.start);
|
||||
event.shared.forEach(other => other.shared.push(event));
|
||||
let occupiedLanes = event.shared.map(other => other.lane);
|
||||
while (occupiedLanes.includes(event.lane))
|
||||
event.lane++;
|
||||
event.maxLane = Math.max(...[event.lane], ...occupiedLanes);
|
||||
if (event.maxLane > 1) {
|
||||
event.setSharedMaxRecursive([event]);
|
||||
}
|
||||
d.events.push(event);
|
||||
});
|
||||
d.lanes = d.events.map(e => e.maxLane).reduce((res, i) => kgv(res, i), 1);
|
||||
}
|
||||
res[key] = d;
|
||||
});
|
||||
return res;
|
||||
},
|
||||
smallestTimeFrame() {
|
||||
return [30,15,10,5][this.size];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeToMonth(day) {
|
||||
if (!this.noMonthView) {
|
||||
this.date.set(day);
|
||||
this.focusDate.set(day);
|
||||
this.$emit('update:mode', 'month');
|
||||
}
|
||||
},
|
||||
dateToMinutesOfDay(day) {
|
||||
return Math.floor((day.getHours() * 60 + day.getMinutes()) / this.smallestTimeFrame) + 1;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => this.$refs.eventcontainer.scrollTop = this.$refs.eventcontainer.scrollHeight / 3 + 1, 0);
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-week-page">
|
||||
<div class="d-flex flex-column border-top">
|
||||
<div class="fhc-calendar-week-page-header border-2 border-bottom text-center d-flex">
|
||||
<div v-for="day in days" :key="day" class="flex-grow-1" :title="day.toLocaleString(undefined, {dateStyle:'short'})">
|
||||
<div class="fw-bold">{{day.toLocaleString(undefined, {weekday: size < 2 ? 'narrow' : (size < 3 ? 'short' : 'long')})}}</div>
|
||||
<a href="#" class="small text-secondary text-decoration-none" @click.prevent="changeToMonth(day)">{{day.toLocaleString(undefined, [{day:'numeric',month:'numeric'},{day:'numeric',month:'numeric'},{day:'numeric',month:'numeric'},{dateStyle:'short'}][this.size])}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="eventcontainer" class="flex-grow-1 overflow-auto">
|
||||
<div class="events">
|
||||
<div class="hours">
|
||||
<div v-for="hour in hours" :key="hour" class="text-muted text-end small" :ref="'hour' + hour">{{hour}}:00</div>
|
||||
</div>
|
||||
<div v-for="day in eventsPerDayAndHour" :key="day" class="day border-start" :style="{'grid-template-columns': 'repeat(' + day.lanes + ', 1fr)', 'grid-template-rows': 'repeat(' + (1440 / smallestTimeFrame) + ', 1fr)'}">
|
||||
<a href="#" v-for="event in day.events" :key="event" class="small rounded overflow-hidden text-decoration-none text-dark" :style="{'grid-column-start': 1+(event.lane-1)*day.lanes/event.maxLane, 'grid-column-end': 1+event.lane*day.lanes/event.maxLane, 'grid-row-start': dateToMinutesOfDay(event.start), 'grid-row-end': dateToMinutesOfDay(event.end), '--test': dateToMinutesOfDay(event.end), background: event.orig.color}" @click.prevent="$emit('input', event.orig)">
|
||||
{{event.orig.title}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import CalendarAbstract from './Abstract.js';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
CalendarAbstract
|
||||
],
|
||||
inject: [
|
||||
'size',
|
||||
'focusDate'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
weeks: [...Array(this.focusDate.numWeeks).keys()].map(i => i+1)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return this.focusDate.format({year: 'numeric'});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setWeek(week) {
|
||||
// TODO(chris): test is there a week jump on year select? => yes there is if the same month/day are in different weeks ... should we prevent that?
|
||||
this.focusDate.w = week;
|
||||
this.$emit('update:mode', 'week');
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-weeks">
|
||||
<calendar-header :title="title" @prev="focusDate.y--" @next="focusDate.y++" @click="$emit('update:mode', 'years')" />
|
||||
<div class="d-flex flex-wrap">
|
||||
<div v-for="(week, key) in weeks" :key="key" class="d-grid col-2">
|
||||
<button @click="setWeek(week)" class="btn btn-outline-secondary" :class="{'border-0': week != focusDate.w}">
|
||||
{{week}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import CalendarAbstract from './Abstract.js';
|
||||
import CalendarPane from './Pane.js';
|
||||
import CalendarYearsPage from './Years/Page.js';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
CalendarAbstract
|
||||
],
|
||||
components: {
|
||||
CalendarYearsPage,
|
||||
CalendarPane
|
||||
},
|
||||
inject: [
|
||||
'size'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
start: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
range() {
|
||||
switch (this.size) {
|
||||
case 3:
|
||||
// eslint-disable-next-line
|
||||
case 2:
|
||||
return 24;
|
||||
}
|
||||
return 12;
|
||||
},
|
||||
end() {
|
||||
return this.start + this.range - 1;
|
||||
},
|
||||
title() {
|
||||
return this.start + ' - ' + this.end;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
paneChanged(dir) {
|
||||
this.start += this.range * dir;
|
||||
},
|
||||
prev() {
|
||||
this.$refs.pane.prev();
|
||||
},
|
||||
next() {
|
||||
this.$refs.pane.next();
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.start = this.focusDate.y - this.focusDate.y%this.range;
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-years">
|
||||
<calendar-header :title="title" @prev="prev" @next="next" @click="$emit('update:mode')" />
|
||||
<calendar-pane ref="pane" v-slot="slot" @slid="paneChanged">
|
||||
<calendar-years-page :data-test="slot.index" :start="start+range*slot.offset" :end="start+range*slot.offset+range" @update:mode="$emit('update:mode')"/>
|
||||
</calendar-pane>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
export default {
|
||||
inject: [
|
||||
'focusDate'
|
||||
],
|
||||
props: {
|
||||
start: Number,
|
||||
end: Number
|
||||
},
|
||||
emits: [
|
||||
'update:mode'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
years() {
|
||||
return [...Array(this.end - this.start).keys()].map(i => i + this.start);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-years-page d-flex flex-wrap">
|
||||
<div v-for="year in years" :key="year" class="d-grid col-4">
|
||||
<button class="btn btn-outline-secondary" :class="{'border-0': year != focusDate.y}" @click="focusDate.y = year; $emit('update:mode')">
|
||||
{{year}}
|
||||
</button>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
class CalendarDate {
|
||||
constructor(y, m, d) {
|
||||
this.weekStart = CalendarDate.getWeekStart();
|
||||
this.set(y, m, d);
|
||||
this._clean();
|
||||
}
|
||||
get y() { return this._y }
|
||||
set y(v) { this._y = v; this._clean() }
|
||||
get m() { return this._m }
|
||||
set m(v) { this._m = v; this._clean() }
|
||||
get d() { return this._d }
|
||||
set d(v) { this._d = v; this._clean() }
|
||||
/**
|
||||
* @see https://www.smart-rechner.de/kalenderwochen/rechner.php
|
||||
*/
|
||||
get w() {
|
||||
if (this._w === null) {
|
||||
if (this.weekStart == 1 && this._m == 11 && this._d > 28 && this.wd <= this._d-29) {
|
||||
this._w = 1;
|
||||
} else if (this.weekStart == 1 && this._m == 0 && this._d < 4 && 3-this.wd <= -this._d) {
|
||||
let weekStartOfTheYear = new Date(this.y-1, 0, this.weekStart == 1 ? 4 : 1);
|
||||
weekStartOfTheYear.setDate(weekStartOfTheYear.getDate() - (weekStartOfTheYear.getDay() + 7 - this.weekStart)%7);
|
||||
this._w = Math.ceil((Math.floor((new Date(this.y, this.m, this.d) - weekStartOfTheYear) / 86400000) + 1) / 7);
|
||||
} else {
|
||||
let weekStartOfTheYear = new Date(this.y, 0, this.weekStart == 1 ? 4 : 1);
|
||||
weekStartOfTheYear.setDate(weekStartOfTheYear.getDate() - (weekStartOfTheYear.getDay() + 7 - this.weekStart)%7);
|
||||
this._w = Math.ceil((Math.floor((new Date(this.y, this.m, this.d) - weekStartOfTheYear) / 86400000) + 1) / 7);
|
||||
}
|
||||
}
|
||||
return this._w;
|
||||
}
|
||||
set w(v) {
|
||||
if (this.w != v) {
|
||||
let lw = this.numWeeks;
|
||||
|
||||
this.d += (v - this.w) * 7;
|
||||
|
||||
if (v > 0 && v <= lw && this.w != v) {
|
||||
if (this.weekStart != 1) {
|
||||
if (this.w == 1) {
|
||||
this.set(this.firstDayOfWeek);
|
||||
} else {
|
||||
this.set(this.lastDayOfWeek);
|
||||
}
|
||||
}
|
||||
if (this.w != v) {
|
||||
console.error('couldn\'t set the week', this, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
get wd() {
|
||||
if (this._wd === null) {
|
||||
this._wd = ((new Date(this.y, this.m, this.d)).getDay()+7-this.weekStart)%7;
|
||||
}
|
||||
return this._wd;
|
||||
}
|
||||
get firstDayOfWeek() {
|
||||
let firstDayOfWeek = new Date(this.y, this.m, this.d);
|
||||
firstDayOfWeek.setDate(this.d -(firstDayOfWeek.getDay()+7-this.weekStart)%7);
|
||||
return firstDayOfWeek;
|
||||
}
|
||||
get cdFirstDayOfWeek() {
|
||||
return new CalendarDate(this.firstDayOfWeek);
|
||||
}
|
||||
get lastDayOfWeek() {
|
||||
let lastDayOfWeek = new Date(this.y, this.m, this.d);
|
||||
lastDayOfWeek.setDate(this.d -(lastDayOfWeek.getDay()+7-this.weekStart)%7 +6);
|
||||
return lastDayOfWeek;
|
||||
}
|
||||
get cdLastDayOfWeek() {
|
||||
return new CalendarDate(this.lastDayOfWeek);
|
||||
}
|
||||
get firstDayOfCalendarMonth() {
|
||||
let firstDayOfMonth = new Date(this.y, this.m, 1);
|
||||
return new Date(this.y, this.m, 1-(firstDayOfMonth.getDay() + 7 - this.weekStart)%7);
|
||||
}
|
||||
get cdFirstDayOfCalendarMonth() {
|
||||
let firstDayOfMonth = new Date(this.y, this.m, 1);
|
||||
return new CalendarDate(this.y, this.m, 1-(firstDayOfMonth.getDay() + 7 - this.weekStart)%7);
|
||||
}
|
||||
get lastDayOfCalendarMonth() {
|
||||
let lastDayOfMonth = new Date(this.y, this.m+1, 0);
|
||||
return new Date(lastDayOfMonth.getFullYear(), lastDayOfMonth.getMonth(), lastDayOfMonth.getDate()+6-(lastDayOfMonth.getDay() + 7 - this.weekStart)%7);
|
||||
}
|
||||
get cdLastDayOfCalendarMonth() {
|
||||
let lastDayOfMonth = new Date(this.y, this.m+1, 0);
|
||||
return new CalendarDate(lastDayOfMonth.getFullYear(), lastDayOfMonth.getMonth(), lastDayOfMonth.getDate()+6-(lastDayOfMonth.getDay() + 7 - this.weekStart)%7);
|
||||
}
|
||||
get numWeeks() {
|
||||
return (new CalendarDate(this.y+1,0,this.weekStart == 1 ? -3 : 0)).w;
|
||||
}
|
||||
set(y,m,d,noClean) {
|
||||
if (y !== undefined && (m === undefined || m === true) && d === undefined) {
|
||||
if (Object.prototype.toString.call(y) === '[object Date]')
|
||||
return this.set(y.getFullYear(), y.getMonth(), y.getDate(), m);
|
||||
if (y.y !== undefined && y.m !== undefined && y.d !== undefined)
|
||||
return this.set(y.y, y.m, y.d, m);
|
||||
}
|
||||
[this._y,this._m,this._d] = [y || 0, m || 0, d || 0];
|
||||
if (!noClean)
|
||||
this._clean();
|
||||
}
|
||||
_clean() {
|
||||
this.set(new Date(this._y, this._m, this._d), true);
|
||||
this._w = null;
|
||||
this._wd = null;
|
||||
}
|
||||
format(options) {
|
||||
return (new Date(this._y, this._m, this._d)).toLocaleString(undefined, options);
|
||||
}
|
||||
compare(d) {
|
||||
if (Object.prototype.toString.call(d) === '[object Date]')
|
||||
return (this.y === d.getFullYear() && this.m === d.getMonth() && this.d === d.getDate());
|
||||
return (this.y === d.y && this.m === d.m && this.d === d.d);
|
||||
}
|
||||
isInWeek(w, y) {
|
||||
if (this.y == y && this.w == w)
|
||||
return true;
|
||||
if (this.weekStart == 1)
|
||||
return false;
|
||||
let edgeDay = this.cdFirstDayOfWeek;console.log(edgeDay);
|
||||
if (edgeDay.y == y && edgeDay.w == w)
|
||||
return true;
|
||||
edgeDay = this.cdLastDayOfWeek;
|
||||
if (edgeDay.y == y && edgeDay.w == w)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
setLocale(locale) {
|
||||
this.weekStart = CalendarDate.getWeekStart(locale);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns the weekday number (Date.getDay()) on which the week starts depending on the locale.
|
||||
* This can be Saturday(6), Sunday(0) or Monday(1)
|
||||
*
|
||||
* @see https://stackoverflow.com/questions/53382465/how-can-i-determine-if-week-starts-on-monday-or-sunday-based-on-locale-in-pure-j
|
||||
*
|
||||
* @param string locale
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
CalendarDate.getWeekStart = function(locale) {
|
||||
locale = locale || navigator.language;
|
||||
const parts = locale.match(/^([a-z]{2,3})(?:-([a-z]{3})(?=$|-))?(?:-([a-z]{4})(?=$|-))?(?:-([a-z]{2}|\d{3})(?=$|-))?/i);
|
||||
const regionSat = 'AEAFBHDJDZEGIQIRJOKWLYOMQASDSY'.match(/../g);
|
||||
const regionSun = 'AGARASAUBDBRBSBTBWBZCACNCODMDOETGTGUHKHNIDILINJMJPKEKHKRLAMHMMMOMTMXMZNINPPAPEPHPKPRPTPYSASGSVTHTTTWUMUSVEVIWSYEZAZW'.match(/../g);
|
||||
const languageSat = ['ar','arq','arz','fa'];
|
||||
const languageSun = 'amasbndzengnguhehiidjajvkmknkolomhmlmrmtmyneomorpapssdsmsnsutatethtnurzhzu'.match(/../g);
|
||||
|
||||
return (
|
||||
parts[4] ? (
|
||||
regionSun.includes(parts[4]) ? 0 :
|
||||
regionSat.includes(parts[4]) ? 6 : 1) : (
|
||||
languageSun.includes(parts[1]) ? 0 :
|
||||
languageSat.includes(parts[1]) ? 6 : 1));
|
||||
}
|
||||
|
||||
|
||||
export default CalendarDate
|
||||
Reference in New Issue
Block a user