mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-25 16:44:28 +00:00
Merge branch 'feature-60973/komponente_fuer_lehrfaecherverteilung' into feature-61232/Studierendenverwaltung_Karteireiter_Projektarbeit_portieren
This commit is contained in:
+331
-56
@@ -1,59 +1,238 @@
|
||||
/* basic */
|
||||
@import '../components/searchbar/searchbar.css';
|
||||
@import '../components/Sprachen.css';
|
||||
@import './Cis/Calendar.css';
|
||||
|
||||
html {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
/* variables */
|
||||
:root {
|
||||
--fhc-cis-primary: #00649c;
|
||||
--fhc-cis-primary-hover: #005585;
|
||||
--fhc-cis-header-px: 1rem;
|
||||
--fhc-cis-header-py: .5rem;
|
||||
--fhc-cis-main-px: 1.5rem;
|
||||
--fhc-cis-main-py: 1.5rem;
|
||||
--fhc-cis-menu-lvl-1-bg: #00649c;
|
||||
--fhc-cis-menu-lvl-1-color: #fff;
|
||||
--fhc-cis-menu-lvl-1-bg-hover: #005585;
|
||||
--fhc-cis-menu-lvl-1-color-hover: #fff;
|
||||
--fhc-cis-menu-lvl-2-bg: #343a40;
|
||||
--fhc-cis-menu-lvl-2-color: #fff;
|
||||
--fhc-cis-menu-lvl-2-bg-hover: #2c3136;
|
||||
--fhc-cis-menu-lvl-2-color-hover: #fff;
|
||||
--fhc-cis-menu-lvl-2-color-active: #292b2a;
|
||||
--fhc-cis-menu-lvl-3-bg: #72777A;
|
||||
--fhc-cis-menu-lvl-3-color: #fff;
|
||||
--fhc-cis-menu-lvl-3-bg-hover: #616568;
|
||||
--fhc-cis-menu-lvl-3-color-hover: #fff;
|
||||
--fhc-cis-menu-lvl-4-bg: #ced4da;
|
||||
--fhc-cis-menu-lvl-4-color: #000;
|
||||
--fhc-cis-menu-lvl-4-bg-hover: #d5dae0;
|
||||
--fhc-cis-menu-lvl-4-color-hover: #000;
|
||||
--fhc-cis-menu-lvl-5-bg: #e9ecef;
|
||||
--fhc-cis-menu-lvl-5-color: #000;
|
||||
--fhc-cis-menu-lvl-5-bg-hover: #eceff1;
|
||||
--fhc-cis-menu-lvl-5-color-hover: #000;
|
||||
--fhc-cis-grade-positive: rgb(0, 128, 0);
|
||||
--fhc-cis-grade-negative: rgb(128, 0, 0);
|
||||
.fhc-dark-bg{
|
||||
background-color: var(--fhc-dark);
|
||||
color:var(--fhc-light) !important;
|
||||
}
|
||||
|
||||
.fhc-dark-color {
|
||||
color: var(--fhc-dark);
|
||||
}
|
||||
|
||||
.fhc-dark-bg:hover{
|
||||
filter: brightness(120%);
|
||||
}
|
||||
|
||||
.fhc-primary{
|
||||
background-color: var(--fhc-primary) !important;
|
||||
color: var(--fhc-light) !important;
|
||||
}
|
||||
|
||||
.fhc-primary-color{
|
||||
color: var(--fhc-primary) !important;
|
||||
}
|
||||
|
||||
.fhc-primary-bg {
|
||||
background-color: var(--fhc-primary) !important;
|
||||
}
|
||||
|
||||
.fhc-primary-highlight-bg {
|
||||
background-color: var(--fhc-primary-highlight) !important;
|
||||
}
|
||||
|
||||
.fhc-primary-highlight-color {
|
||||
color: var(--fhc-primary-highlight) !important;
|
||||
}
|
||||
|
||||
.fhc-body-bg{
|
||||
background-color:var(--fhc-background) !important;
|
||||
}
|
||||
|
||||
.fhc-body-color{
|
||||
color: var(--fhc-text) !important;
|
||||
}
|
||||
|
||||
.fhc-secondary-bg{
|
||||
background-color: var(--fhc-tertiary) !important;
|
||||
}
|
||||
|
||||
.fhc-secondary-color {
|
||||
color: var(--fhc-tertiary) !important;
|
||||
}
|
||||
|
||||
.fhc-tertiary-bg{
|
||||
background-color: var(--fhc-secondary) !important;
|
||||
}
|
||||
|
||||
.fhc-tertiary-color {
|
||||
color: var(--fhc-secondary) !important;
|
||||
}
|
||||
|
||||
.fhc-fourth-bg {
|
||||
background-color: var(--fhc-fourth) !important;
|
||||
}
|
||||
|
||||
.fhc-fourth-color {
|
||||
color: var(--fhc-fourth) !important;
|
||||
}
|
||||
|
||||
.fhc-fifth-bg {
|
||||
background-color: var(--fhc-fifth) !important;
|
||||
}
|
||||
|
||||
.fhc-fifth-color {
|
||||
color: var(--fhc-fifth) !important;
|
||||
}
|
||||
|
||||
.fhc-body{
|
||||
color:var(--fhc-text);
|
||||
background-color: var(--fhc-background);
|
||||
border-color: var(--fhc-border);
|
||||
}
|
||||
|
||||
.fhc-secondary {
|
||||
color: var(--fhc-text);
|
||||
background-color: var(--fhc-tertiary);
|
||||
border-color: var(--fhc-border);
|
||||
}
|
||||
|
||||
.fhc-tertiary{
|
||||
color: var(--fhc-text);
|
||||
background-color: var(--fhc-secondary);
|
||||
border-color:var(--fhc-border);
|
||||
}
|
||||
|
||||
.fhc-link-color{
|
||||
color: var(--fhc-link);
|
||||
}
|
||||
|
||||
.fhc-text{
|
||||
color: var(--fhc-text) !important;
|
||||
}
|
||||
|
||||
.fhc-text-light{
|
||||
color: var(--fhc-light) !important;
|
||||
}
|
||||
|
||||
:root{
|
||||
/* sidebar colors */
|
||||
--fhc-cis-sidebar-bg: var(--fhc-secondary);
|
||||
--fhc-cis-menu-bg: var(--fhc-secondary);
|
||||
|
||||
--fhc-cis-header-px: 1rem;
|
||||
--fhc-cis-header-py: .5rem;
|
||||
--fhc-cis-main-px: 1.5rem;
|
||||
--fhc-cis-main-py: 1.5rem;
|
||||
|
||||
--fhc-cis-menu-lvl-1-bg: var(--fhc-primary);
|
||||
--fhc-cis-menu-lvl-1-color: var(--fhc-light);
|
||||
--fhc-cis-menu-lvl-1-bg-hover: var(--fhc-primary-highlight);
|
||||
--fhc-cis-menu-lvl-1-color-hover: var(--fhc-light);
|
||||
|
||||
--fhc-cis-menu-lvl-2-bg: var(--fhc-secondary);
|
||||
--fhc-cis-menu-lvl-2-color: var(--fhc-text);
|
||||
--fhc-cis-menu-lvl-2-bg-hover: var(--fhc-secondary-highlight);
|
||||
--fhc-cis-menu-lvl-2-color-hover: var(--fhc-text);
|
||||
|
||||
--fhc-cis-menu-lvl-3-bg: var(--fhc-tertiary);
|
||||
--fhc-cis-menu-lvl-3-color: var(--fhc-text);
|
||||
--fhc-cis-menu-lvl-3-bg-hover: var(--fhc-tertiary-highlight);
|
||||
--fhc-cis-menu-lvl-3-color-hover: var(--fhc-text);
|
||||
|
||||
--fhc-cis-menu-lvl-4-bg: var(--fhc-fourth);
|
||||
--fhc-cis-menu-lvl-4-color: var(--fhc-text);
|
||||
--fhc-cis-menu-lvl-4-bg-hover: var(--fhc-fourth-highlight);
|
||||
--fhc-cis-menu-lvl-4-color-hover: var(--fhc-text);
|
||||
|
||||
--fhc-cis-menu-lvl-5-bg: var(--fhc-fifth);
|
||||
--fhc-cis-menu-lvl-5-color: var(--fhc-text);
|
||||
--fhc-cis-menu-lvl-5-bg-hover: var(--fhc-fifth-highlight);
|
||||
--fhc-cis-menu-lvl-5-color-hover: var(--fhc-text);
|
||||
--fhc-cis-grade-positive: var(--fhc-success);
|
||||
--fhc-cis-grade-negative: var(--fhc-danger);
|
||||
}
|
||||
|
||||
#themeSwitch i{
|
||||
color: var(fhc-light) !important;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
:root {
|
||||
--fhc-cis-menu-width: 250px;
|
||||
--fhc-cis-header-height: 60px;
|
||||
--fhc-cis-menu-bg: #72777A;
|
||||
}
|
||||
:root {
|
||||
--fhc-cis-menu-width: 250px;
|
||||
--fhc-cis-header-height: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
:root {
|
||||
--fhc-cis-menu-width: 400px;
|
||||
--fhc-cis-header-height: 40px;
|
||||
--fhc-cis-menu-bg: #343a40;
|
||||
}
|
||||
:root {
|
||||
--fhc-cis-menu-width: 400px;
|
||||
--fhc-cis-header-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
/* lvMenu entry style */
|
||||
#cis-menu .menu-entry {
|
||||
color: var(--fhc-text);
|
||||
}
|
||||
|
||||
#cis-menu .menu-entry .submenu{
|
||||
background-color: var(--fhc-background-highlight);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#cis-menu .menu-entry:hover .submenu{
|
||||
background-color: var(--fhc-primary-highlight);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#cis-menu .menu-entry:hover .submenu:hover{
|
||||
color: var(--fhc-dark);
|
||||
}
|
||||
|
||||
#cis-menu [disabled="true"].menu-entry .submenu{
|
||||
opacity: 0.5;
|
||||
background-color: var(--fhc-secondary)
|
||||
}
|
||||
|
||||
[disabled="true"].menu-entry {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
background-color: var(--fhc-disabled) !important;
|
||||
color: var(--fhc-dark) !important;
|
||||
opacity: 0.75;
|
||||
-webkit-touch-callout: none;
|
||||
/* iOS Safari */
|
||||
-webkit-user-select: none;
|
||||
/* Safari */
|
||||
-khtml-user-select: none;
|
||||
/* Konqueror HTML */
|
||||
-moz-user-select: none;
|
||||
/* Old versions of Firefox */
|
||||
-ms-user-select: none;
|
||||
/* Internet Explorer/Edge */
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#cis-menu .menu-entry:hover{
|
||||
background-color: var(--fhc-primary);
|
||||
color:var(--fhc-light);
|
||||
}
|
||||
|
||||
/* lvMenu entry disabled style */
|
||||
[disabled="true"].menu-entry {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
background-color: #dadada !important;
|
||||
color: black !important;
|
||||
-webkit-touch-callout: none;
|
||||
/* iOS Safari */
|
||||
-webkit-user-select: none;
|
||||
/* Safari */
|
||||
-khtml-user-select: none;
|
||||
/* Konqueror HTML */
|
||||
-moz-user-select: none;
|
||||
/* Old versions of Firefox */
|
||||
-ms-user-select: none;
|
||||
/* Internet Explorer/Edge */
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* buttons */
|
||||
#cis-header .btn-level-1,
|
||||
@@ -195,6 +374,7 @@ html {
|
||||
|
||||
/* searchbar */
|
||||
#nav-search {
|
||||
background-color: var(--fhc-primary);
|
||||
z-index: 1;
|
||||
}
|
||||
#nav-search.me-3 {
|
||||
@@ -223,9 +403,16 @@ html {
|
||||
}
|
||||
|
||||
#cis-main {
|
||||
color: var(--fhc-text);
|
||||
background-color: var(--fhc-background);
|
||||
border-color: var(--fhc-border);
|
||||
padding: var(--fhc-cis-main-py) var(--fhc-cis-main-px);
|
||||
}
|
||||
|
||||
#cis-main .fa-arrow-up-right-from-square {
|
||||
color: var(--fhc-link) !important;
|
||||
}
|
||||
|
||||
#nav-main-sticky {
|
||||
max-height: calc(100vh - var(--fhc-cis-header-height));
|
||||
}
|
||||
@@ -245,10 +432,6 @@ html {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.fhc-active {
|
||||
background-color: var(--fhc-cis-menu-lvl-2-color-active) !important;
|
||||
}
|
||||
|
||||
.fhc-seperator {
|
||||
position: relative;
|
||||
}
|
||||
@@ -261,7 +444,7 @@ html {
|
||||
margin-left: 12.5%;
|
||||
width: 75%; /* Full width of the parent element */
|
||||
height: 1px; /* Height of the border */
|
||||
background: linear-gradient(to right, #ffffff, var(--fhc-cis-primary), #ffffff);
|
||||
background: linear-gradient(to right, var(--fhc-background), var(--fhc-primary), var(--fhc-background));
|
||||
opacity: 0.8; /* Adjust opacity for a subtle fade effect */
|
||||
}
|
||||
|
||||
@@ -295,7 +478,7 @@ html {
|
||||
left: 0;
|
||||
height: var(--fhc-cis-header-height);
|
||||
width: var(--fhc-cis-menu-width);
|
||||
background-color: var(--fhc-cis-primary);
|
||||
background-color: var(--fhc-primary);
|
||||
padding: var(--fhc-cis-header-py) var(--fhc-cis-header-px);
|
||||
z-index: 2;
|
||||
}
|
||||
@@ -327,7 +510,7 @@ html {
|
||||
}
|
||||
#nav-main-toggle .collapsed .fa-arrow-circle-left {
|
||||
transform: scaleX(-1);
|
||||
color: var(--bs-dark);
|
||||
color: var(--fhc-black-40);
|
||||
}
|
||||
#nav-search {
|
||||
position: fixed;
|
||||
@@ -345,7 +528,7 @@ html {
|
||||
#nav-user-btn {
|
||||
border-width: 0;
|
||||
padding: var(--fhc-cis-header-py) var(--fhc-cis-header-px);
|
||||
background-color: var(--fhc-cis-primary);
|
||||
background-color: var(--fhc-primary);
|
||||
}
|
||||
#nav-user-btn img {
|
||||
object-fit: contain;
|
||||
@@ -355,7 +538,7 @@ html {
|
||||
#nav-user-menu {
|
||||
position: absolute;
|
||||
min-width: var(--fhc-cis-menu-width);
|
||||
background-color: var(--fhc-cis-menu-lvl-2-bg);
|
||||
background-color: var(--fhc-dark);
|
||||
}
|
||||
|
||||
#nav-user-menu img {
|
||||
@@ -385,12 +568,13 @@ html {
|
||||
@media (max-width: 991.98px) {
|
||||
#cis-main{
|
||||
padding: 0.75rem 0.75rem;
|
||||
height: calc(100vh - var(--fhc-cis-header-height));
|
||||
}
|
||||
#cis-main > :first-child {
|
||||
margin-top: var(--fhc-cis-header-height);
|
||||
}
|
||||
#cis-header {
|
||||
background-color: var(--fhc-cis-primary);
|
||||
background-color: var(--fhc-primary);
|
||||
height: var(--fhc-cis-header-height);
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
@@ -427,18 +611,23 @@ html {
|
||||
}
|
||||
#nav-user-menu {
|
||||
position: absolute;
|
||||
background-color: var(--fhc-cis-menu-lvl-2-bg);
|
||||
background-color: var(--fhc-dark);
|
||||
}
|
||||
#nav-main-menu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
#nav-main{
|
||||
background-color: var(--fhc-cis-sidebar-bg);
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
.fhc-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: end;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
border-bottom: 1px solid var(--fhc-border);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
.fhc-header > h1 {
|
||||
@@ -450,7 +639,7 @@ html {
|
||||
}
|
||||
}
|
||||
.fhc-header > h1 > small {
|
||||
color: #6c757d;
|
||||
color: var(--fhc-tertiary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
.fhc-header > h1 > small:before {
|
||||
@@ -461,7 +650,7 @@ html {
|
||||
text-decoration: none;
|
||||
}
|
||||
.fhc-header > a:after {
|
||||
color: var(--bs-body-color);
|
||||
color: var(--fhc-text);
|
||||
content: "\f059";
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
@@ -478,7 +667,7 @@ html {
|
||||
[disabled="true"].fhc-entry {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
background-color: #dadada !important;
|
||||
background-color: var(--fhc-disabled) !important;
|
||||
color: black !important;
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
-webkit-user-select: none;/* Safari */
|
||||
@@ -579,4 +768,90 @@ html {
|
||||
|
||||
.dashboard-item {
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
.abgabe-zieldatum-border-red,
|
||||
.abgabe-zieldatum-border-yellow,
|
||||
.abgabe-zieldatum-border-pink,
|
||||
.abgabe-zieldatum-border-green {
|
||||
border: solid 3px lightgrey;
|
||||
}
|
||||
|
||||
.abgabe-zieldatum-border-red {
|
||||
border-color: red;
|
||||
}
|
||||
|
||||
.abgabe-zieldatum-border-yellow {
|
||||
border-color: yellow;
|
||||
}
|
||||
|
||||
.abgabe-zieldatum-border-pink {
|
||||
border-color: pink;
|
||||
}
|
||||
|
||||
.abgabe-zieldatum-border-green {
|
||||
border-color: green;
|
||||
}
|
||||
|
||||
.abgabe-zieldatum-overlay {
|
||||
top: 3px;
|
||||
right: 3px;
|
||||
}
|
||||
|
||||
.abgabe-zieldatum-border-red .abgabe-zieldatum-overlay {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.abgabe-zieldatum-border-yellow .abgabe-zieldatum-overlay {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
.abgabe-zieldatum-border-pink .abgabe-zieldatum-overlay {
|
||||
color: pink;
|
||||
}
|
||||
|
||||
.abgabe-zieldatum-border-green .abgabe-zieldatum-overlay {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.stgkontaktinfo {
|
||||
display: grid;
|
||||
grid-template-columns: 25px auto;
|
||||
word-wrap: anywhere;
|
||||
}
|
||||
|
||||
.mylv-semester-studiengang-lv .card-header {
|
||||
min-height: 55px;
|
||||
}
|
||||
|
||||
#lektorenContainer{
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
|
||||
}
|
||||
|
||||
@media(max-width: 990px) {
|
||||
#lektorenContainer{
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: 575px) {
|
||||
#lektorenContainer {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
#cis-main .modal-header{
|
||||
background-color: var(--fhc-primary);
|
||||
color: var(--fhc-light);
|
||||
}
|
||||
|
||||
#cis-main .modal-header .btn-close{
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
#cis-main .modal-footer {
|
||||
background-color: var(--fhc-secondary);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/* mobile */
|
||||
@media (max-width: 991.98px) {
|
||||
.responsive-calendar .fhc-calendar-mode-week .fhc-calendar-base-label-day .short,
|
||||
.responsive-calendar .fhc-calendar-mode-week .fhc-calendar-base-label-dow .short,
|
||||
.responsive-calendar .fhc-calendar-mode-month .fhc-calendar-base-label-dow .short {
|
||||
display: block;
|
||||
}
|
||||
.responsive-calendar .fhc-calendar-mode-week .fhc-calendar-base-label-day .long,
|
||||
.responsive-calendar .fhc-calendar-mode-week .fhc-calendar-base-label-dow .long,
|
||||
.responsive-calendar .fhc-calendar-mode-month .fhc-calendar-base-label-dow .long {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -11,14 +11,14 @@
|
||||
margin-inline-start: 0 !important;
|
||||
}
|
||||
#cms a.btn-outline-primary {
|
||||
color: var(--fhc-cis-primary);
|
||||
border-color: var(--fhc-cis-primary);
|
||||
color: var(--fhc-primary);
|
||||
border-color: var(--fhc-primary);
|
||||
}
|
||||
#cms a.btn-outline-primary:hover,
|
||||
#cms a.btn-outline-primary:focus {
|
||||
color: #fff;
|
||||
background-color: var(--fhc-cis-primary);
|
||||
border-color: var(--fhc-cis-primary);
|
||||
background-color: var(--fhc-primary);
|
||||
border-color: var(--fhc-primary);
|
||||
}
|
||||
#cms a.btn-primary {
|
||||
color: var(--fhc-cis-menu-lvl-1-color);
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/* All */
|
||||
.weekPageContainer,
|
||||
.dayPageContainer {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Feiertage */
|
||||
.cis-renderer-feiertage-calendar-event {
|
||||
align-items: center;
|
||||
color: var(--fhc-light);
|
||||
}
|
||||
.monthPageContainer .cis-renderer-feiertage-calendar-event,
|
||||
.weekPageContainer .cis-renderer-feiertage-calendar-event,
|
||||
.dayPageContainer .cis-renderer-feiertage-calendar-event {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
justify-items: center;
|
||||
}
|
||||
.listPageContainer .cis-renderer-feiertage-calendar-event {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.monthPageContainer .cis-renderer-feiertage-calendar-event .event-icon {
|
||||
margin: 0 .5rem;
|
||||
}
|
||||
.weekPageContainer .cis-renderer-feiertage-calendar-event .event-icon,
|
||||
.dayPageContainer .cis-renderer-feiertage-calendar-event .event-icon,
|
||||
.listPageContainer .cis-renderer-feiertage-calendar-event .event-icon {
|
||||
margin: .5rem;
|
||||
}
|
||||
|
||||
.listPageContainer .cis-renderer-feiertage-calendar-event .event-title {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* Lehreinheit */
|
||||
/* Reservierungen */
|
||||
.monthPageContainer .calendar-event-default {
|
||||
padding: 0!important;
|
||||
}
|
||||
.monthPageContainer .calendar-event-default .event-time,
|
||||
.monthPageContainer .calendar-event-default .event-lectors {
|
||||
display: none!important;
|
||||
}
|
||||
.monthPageContainer .calendar-event-default .event-text {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
}
|
||||
.weekPageContainer .calendar-event-default {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
.weekPageContainer .calendar-event-default .event-time {
|
||||
border-inline-end: solid 1px var(--bs-secondary);
|
||||
align-self: stretch;
|
||||
align-content: center;
|
||||
padding: .25em .5em .25em .25em;
|
||||
margin-inline-end: .25rem;
|
||||
}
|
||||
.weekPageContainer .calendar-event-default .event-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
}
|
||||
.dayPageContainer .calendar-event-default,
|
||||
.listPageContainer .calendar-event-default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.dayPageContainer .calendar-event-default .event-time,
|
||||
.listPageContainer .calendar-event-default .event-time {
|
||||
align-content: center;
|
||||
padding-inline-end: .5em;
|
||||
border-inline-end: solid 1px;
|
||||
margin-inline-end: .5em;
|
||||
}
|
||||
.dayPageContainer .calendar-event-default .event-text,
|
||||
.listPageContainer .calendar-event-default .event-text {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: .25em;
|
||||
justify-content: center;
|
||||
}
|
||||
.dayPageContainer .calendar-event-default .event-topic,
|
||||
.dayPageContainer .calendar-event-default .event-place,
|
||||
.listPageContainer .calendar-event-default .event-topic,
|
||||
.listPageContainer .calendar-event-default .event-place {
|
||||
flex: 0 0 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fhc-roominformation .calendar-event-default .event-place {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
.lvUebersicht{
|
||||
display:grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.lvUebersicht {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1439px){
|
||||
.lvUebersicht{
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px){
|
||||
.lvUebersicht{
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.lvOptions {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.lvOptions > div {
|
||||
flex-basis: auto;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
}
|
||||
|
||||
.lvOptions button {
|
||||
flex-shrink: 0;
|
||||
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.lvOptions {
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1439px) {
|
||||
.lvOptions {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.lvOptions {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+54
-2
@@ -10,7 +10,7 @@
|
||||
font-size: calc(1.325rem + .9vw);
|
||||
}
|
||||
.fhc-header > :first-child > small {
|
||||
color: var(--bs-secondary);
|
||||
color: var(--fhc-tertiary);
|
||||
font-size: .65em;
|
||||
padding-inline-start: 1em;
|
||||
}
|
||||
@@ -104,4 +104,56 @@
|
||||
}
|
||||
.modal-msg .modal-content {
|
||||
height: 95vh;
|
||||
}
|
||||
}
|
||||
|
||||
.fhcSkipLink {
|
||||
position: absolute;
|
||||
left: -10px;
|
||||
top: -10px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fhcSkipLink::before {
|
||||
content: "Skip to main content"
|
||||
}
|
||||
|
||||
.fhcSkipLink:focus {
|
||||
position: absolute;
|
||||
z-index: 9999999;
|
||||
top: var(--fhc-cis-header-height);
|
||||
left: var(--fhc-cis-menu-width);
|
||||
height: 100px;
|
||||
width: 800px;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #0044cc;
|
||||
background-color: #ffeb3b;
|
||||
text-decoration: none;
|
||||
padding: 12px 18px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.2);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.fhcSkipLink:hover {
|
||||
background-color: #ffcc00;
|
||||
color: #002b80;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.fhc-bullet::before {
|
||||
content: "• ";
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
transform: scale(2);
|
||||
}
|
||||
|
||||
.fhc-bullet-red::before {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.fhc-bullet-green::before {
|
||||
color: red;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
:root{
|
||||
--filter-widget-border: var(--fhc-dark, black);
|
||||
--filter-widget-color: var(--fhc-dark, black);
|
||||
--filter-widget-primary: var(--fhc-primary, #428bca);
|
||||
--filter-widget-border-light: var(--fhc-border, #ddd);
|
||||
}
|
||||
|
||||
.filter-name-title {
|
||||
font-family: inherit;
|
||||
font-size: 16px;
|
||||
line-height: 1.1;
|
||||
color: black;
|
||||
color: var(--filter-widget-color);
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
@@ -26,7 +33,7 @@
|
||||
}
|
||||
|
||||
.drag-and-drop-fields-span {
|
||||
border: 1px solid black;
|
||||
border: 1px solid var(--filter-widget-border);
|
||||
border-radius: 7px;
|
||||
margin-right: 6px;
|
||||
margin-bottom: 10px;
|
||||
@@ -64,7 +71,7 @@
|
||||
right: 100%;
|
||||
height: 100%;
|
||||
margin-right: 3px;
|
||||
border-left: 2px solid #428bca;
|
||||
border-left: 2px solid var(--filter-widget-primary);
|
||||
}
|
||||
|
||||
.selection-after::after {
|
||||
@@ -74,7 +81,7 @@
|
||||
left: 100%;
|
||||
height: 100%;
|
||||
margin-left: 3px;
|
||||
border-right: 2px solid #428bca;
|
||||
border-right: 2px solid var(--filter-widget-primary);
|
||||
}
|
||||
|
||||
.applied-filter-operation {
|
||||
@@ -139,10 +146,10 @@
|
||||
|
||||
.up-down-border {
|
||||
border-top-width: 1px;
|
||||
border-top-color: #ddd;
|
||||
border-top-color: var(--filter-widget-border-light);
|
||||
border-top-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: #ddd;
|
||||
border-bottom-color: var(--filter-widget-border-light);
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,14 +14,17 @@ textarea[name="anmerkung"] {
|
||||
.lv_table_icon.icon-modul::before
|
||||
{
|
||||
content: '\f468';
|
||||
color: #4e4ed2;
|
||||
}
|
||||
|
||||
.lv_table_icon.icon-lv::before
|
||||
{
|
||||
content: '\f466';
|
||||
color: black;
|
||||
}
|
||||
|
||||
.lv_table_icon.icon-::before
|
||||
{
|
||||
content: '\f073';
|
||||
}
|
||||
color: #f3c541;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
:root{
|
||||
--fhc-filtercomponent-primary: var(--fhc-primary, #428bca);
|
||||
--fhc-filtercomponent-bg:var(--fhc-background, white);
|
||||
--fhc-filtercomponent-border:var(--fhc-border, black);
|
||||
}
|
||||
|
||||
.filter-header-title {
|
||||
text-align: right;
|
||||
}
|
||||
@@ -26,9 +32,9 @@
|
||||
margin-left: 7px;
|
||||
padding: 11px;
|
||||
display: inline-block;
|
||||
background-color: white;
|
||||
background-color: var(--fhc-filtercomponent-bg);
|
||||
border-style: solid;
|
||||
border-color: black;
|
||||
border-color: var(--fhc-filtercomponent-border);
|
||||
border-width: 1px;
|
||||
border-radius: 7px;
|
||||
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||
@@ -47,7 +53,7 @@
|
||||
right: 100%;
|
||||
height: 100%;
|
||||
margin-right: 3px;
|
||||
border-left: 2px solid #428bca;
|
||||
border-left: 2px solid var(--fhc-filtercomponent-primary);
|
||||
}
|
||||
|
||||
.selection-after::after {
|
||||
@@ -57,7 +63,7 @@
|
||||
left: 100%;
|
||||
height: 100%;
|
||||
margin-left: 3px;
|
||||
border-right: 2px solid #428bca;
|
||||
border-right: 2px solid var(--fhc-filtercomponent-primary);
|
||||
}
|
||||
|
||||
.filter-table-dataset {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
/* FORM UNDERLINE
|
||||
*/
|
||||
:root {
|
||||
--fhc-formunderline-border: var(--fhc-border, #dee2e6);
|
||||
--fhc-formunderline-link: var(--fhc-link, initial);
|
||||
--fhc-formunderline-titel-color: var(--fhc-text,gray);
|
||||
}
|
||||
|
||||
.form-underline{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -8,11 +14,13 @@
|
||||
|
||||
}
|
||||
|
||||
|
||||
a.form-underline-content{
|
||||
color: var(--fhc-formunderline-link);
|
||||
}
|
||||
|
||||
.form-underline .form-underline-content{
|
||||
border-width: 1px;
|
||||
border-color: transparent transparent #dee2e6 transparent;
|
||||
border-color: transparent transparent var(--fhc-formunderline-border) transparent;
|
||||
border-style: solid;
|
||||
padding-bottom: 0.25rem;
|
||||
padding-left: 0.5rem;
|
||||
@@ -32,4 +40,5 @@
|
||||
opacity: 0.65;
|
||||
font-size: .85rem;
|
||||
padding-left: 0.5rem;
|
||||
color: var(--fhc-formunderline-titel-color) !important;
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
:root{
|
||||
--fhc-myLv-disabled: var(--fhc-disabled, #565e647f);
|
||||
}
|
||||
|
||||
.unavailable, .unavailable:hover{
|
||||
color: #565e647f !important;
|
||||
color: var(--fhc-myLv-disabled) !important;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
/*
|
||||
* Hederer
|
||||
*/
|
||||
|
||||
:root{
|
||||
--fhc-nav-component-bg: var(--fhc-tertiary, #f8f8f8);
|
||||
--fhc-nav-component-border: var(--fhc-border, #e7e7e7);
|
||||
--fhc-nav-component-color: var(--fhc-text, #5e5e5e);
|
||||
--fhc-nav-left-component-border: var(--fhc-border, #e7e7e7);
|
||||
--fhc-nav-left-component-bg: var(--fhc-tertiary, #f8f8f8);
|
||||
--fhc-nav-left-component-color: var(--fhc-primary, #23527c);
|
||||
--fhc-nav-left-component-hover: var(--fhc-secondary-highlight, #eee);
|
||||
}
|
||||
|
||||
.navbar-header {
|
||||
background-color: #f8f8f8;
|
||||
background-color: var(--fhc-nav-component-bg);
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
padding-left: 10px;
|
||||
border-style: solid;
|
||||
border-color: #e7e7e7;
|
||||
border-color: var(--fhc-nav-component-border);
|
||||
border-width: 0 0 1px;
|
||||
}
|
||||
|
||||
@@ -15,7 +26,7 @@
|
||||
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||
font-size: 18px;
|
||||
line-height: 1.42857143;
|
||||
color: #5e5e5e !important;
|
||||
color: var(--fhc-nav-component-color) !important;
|
||||
padding-top: 15px !important;
|
||||
padding-right: 7px !important;
|
||||
padding-left: 7px !important;
|
||||
@@ -34,8 +45,8 @@
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
width: 250px;
|
||||
border-right: 1px solid #e7e7e7;
|
||||
background-color: #f8f8f8;
|
||||
border-right: 1px solid var(--fhc-nav-left-component-border);
|
||||
background-color:var(--fhc-nav-left-component-bg);
|
||||
}
|
||||
|
||||
.navbar-left-side ul {
|
||||
@@ -43,7 +54,7 @@
|
||||
}
|
||||
|
||||
.navbar-left-side ul li {
|
||||
border-bottom: 1px solid #e7e7e7;
|
||||
border-bottom: 1px solid var(--fhc-nav-left-component-border);
|
||||
}
|
||||
|
||||
.left-side-menu-second-level {
|
||||
@@ -67,7 +78,7 @@
|
||||
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.42857143;
|
||||
color: #23527c;
|
||||
color: var(--fhc-nav-left-component-color);
|
||||
padding-top: 10px !important;
|
||||
padding-bottom: 10px !important;
|
||||
padding-left: 7px !important;
|
||||
@@ -76,7 +87,7 @@
|
||||
}
|
||||
|
||||
.left-side-menu-link-entry:hover {
|
||||
background-color: #eee;
|
||||
background-color: var(--fhc-nav-left-component-hover);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -88,4 +99,3 @@
|
||||
margin: 0 0 0 250px;
|
||||
padding: 30px 30px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
|
||||
|
||||
|
||||
:root{
|
||||
--fhc-profil-border: var(--fhc-background-highlight, gray);
|
||||
--fhc-profil-tabulator-shadow: var(--fhc-shadow-20, rgba(0,0,0,0.1));
|
||||
}
|
||||
|
||||
.image-lock{
|
||||
height:22px;
|
||||
width:21px;
|
||||
background-color:white;
|
||||
background-color:var(--fhc-profil-border);
|
||||
position:absolute;
|
||||
/* top: 1px is important, otherwise it goes over the border of the thumbnail*/
|
||||
top:1px;
|
||||
right:12px;
|
||||
display:flex;
|
||||
@@ -16,9 +17,22 @@
|
||||
border-radius:3px;
|
||||
}
|
||||
|
||||
.image-upload {
|
||||
height: 22px;
|
||||
width: 21px;
|
||||
background-color: var(--fhc-profil-border);
|
||||
position: absolute;
|
||||
bottom: 4.5px;
|
||||
left: 14.5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 3px 3px 3px 0;
|
||||
}
|
||||
|
||||
.tabulator-collapsed-row{
|
||||
padding:15px;
|
||||
background-color: rgba(0,0,0,0.1);
|
||||
background-color: var(--fhc-profil-tabulator-shadow);
|
||||
|
||||
}
|
||||
|
||||
@@ -30,9 +44,6 @@
|
||||
padding:0 !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.btn-circle {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
@@ -42,7 +53,3 @@
|
||||
font-size: 12px;
|
||||
line-height: 1.42857;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
|
||||
:root {
|
||||
--fhc-sprachen-color: var(--fhc-text, white);
|
||||
--fhc-sprachen-background: var(--fhc-primary, #005585);
|
||||
--fhc-sprachen-background-highlight: var(--fhc-primary-highlight, #00649c);
|
||||
}
|
||||
|
||||
.sprachen-entry{
|
||||
background-color: var(--fhc-cis-primary-hover);
|
||||
color: var(--fhc-sprachen-color);
|
||||
background-color: var(--fhc-sprachen-background);
|
||||
}
|
||||
|
||||
.sprachen-entry:hover {
|
||||
background-color: var(--fhc-sprachen-background);
|
||||
}
|
||||
|
||||
[selected="true"].sprachen-entry {
|
||||
background-color: var(--fhc-cis-primary);
|
||||
background-color: var(--fhc-sprachen-background-highlight);
|
||||
}
|
||||
|
||||
.sprachen-entry.btn {
|
||||
|
||||
@@ -1,247 +1,41 @@
|
||||
:root{
|
||||
--fhc-calendar-pane-height: calc(100vh - 220px);
|
||||
--fhc-calendar-past: #F5E9D7
|
||||
}
|
||||
@import './calendar/frame.css';
|
||||
@import './calendar/events.css';
|
||||
@import './calendar/backgrounds.css';
|
||||
|
||||
.fhc-calendar-week-page-header {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
/* Themable Variables */
|
||||
:root {
|
||||
--fhc-calendar-gap: var(--bs-border-width, 1px);
|
||||
|
||||
.fhc-calendar-week-page-header > div:hover{
|
||||
background-color: #dddfe2;
|
||||
}
|
||||
--fhc-calendar-bg: var(--fhc-background, #fff);
|
||||
--fhc-calendar-border: var(--fhc-border, #dee2e6);
|
||||
--fhc-calendar-bg-gap: var(--fhc-border, #dee2e6);
|
||||
|
||||
.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-bg-event: var(--fhc-white-80, #ccc);
|
||||
--fhc-calendar-border-event: var(--bs-secondary, #6c757d);
|
||||
--fhc-calendar-radius-event: var(--bs-border-radius, .375rem);
|
||||
|
||||
.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-bg-markings-past: var(--fhc-beige-10, rgba(245, 233, 215, 0.5));
|
||||
--fhc-calendar-border-markings-past: var(--fhc-calendar-border);
|
||||
--fhc-calendar-bg-markings-past-label: var(--fhc-calendar-bg);
|
||||
--fhc-calendar-border-markings-past-label: var(--fhc-calendar-border);
|
||||
|
||||
.fhc-calendar-month-page-weekday,
|
||||
.fhc-calendar-month-page-day {
|
||||
color: inherit;
|
||||
}
|
||||
--fhc-calendar-bg-header: var(--fhc-calendar-bg);
|
||||
--fhc-calendar-bg-header-gap: transparent;
|
||||
--fhc-calendar-bg-allday: var(--fhc-calendar-bg);
|
||||
--fhc-calendar-bg-allday-gap: transparent;
|
||||
--fhc-calendar-bg-body: var(--fhc-calendar-bg);
|
||||
--fhc-calendar-bg-body-gap: transparent;
|
||||
|
||||
.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);*/
|
||||
max-height: 100%;
|
||||
}
|
||||
.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%;*/
|
||||
}
|
||||
.fhc-calendar-week-page .events .day {
|
||||
gap: 1px;
|
||||
}
|
||||
.fhc-calendar-week-page .events .hours, .fhc-calendar-week-page .events .day {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
/* grid hour lines of the Stundenplan use box-shadow instead of border because box-shadow renders consistently on different viewports*/
|
||||
.box-shadow-border-top{
|
||||
box-shadow: 0 0 0 1px #dee2e6 !important;
|
||||
}
|
||||
|
||||
.fhc-calendar-no-events-overlay{
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fhc-calendar-no-events-overlay::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin:auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: linear-gradient(120deg, white, gray );
|
||||
opacity: .7;
|
||||
}
|
||||
.fhc-calendar-day-page {
|
||||
/*aspect-ratio: 7/6;*/
|
||||
min-height: 0;
|
||||
}
|
||||
.fhc-calendar-day-page > div {
|
||||
/*transform: translate(-0.75em, -0.75em);*/
|
||||
/*width: calc(100% + 1.5em);*/
|
||||
height: calc(100% + 1.5em);
|
||||
}
|
||||
.fhc-calendar-day-page .flex-column > .flex-grow-1 {
|
||||
padding-left: 3em;
|
||||
}
|
||||
.fhc-calendar-day-page .flex-column {
|
||||
border-right: 1px solid #dee2e6;
|
||||
}
|
||||
.fhc-calendar-day-page .events {
|
||||
display: grid;
|
||||
grid-template-columns: 3em 1fr;
|
||||
/*min-height: 266.6666666667%;*/
|
||||
}
|
||||
.fhc-calendar-day-page .events .day {
|
||||
gap: 1px;
|
||||
}
|
||||
.fhc-calendar-day-page .events .hours, .fhc-calendar-day-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: auto;*/
|
||||
/* 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-month-page-weekday:hover {
|
||||
background-color:#37789c;
|
||||
color:white;
|
||||
--fhc-calendar-bg-month-today: var(--fhc-primary);
|
||||
--fhc-calendar-color-month-today: var(--fhc-white-10);
|
||||
|
||||
}
|
||||
--fhc-calendar-bg-overlay1: var(--fhc-calendar-bg);
|
||||
--fhc-calendar-bg-overlay2: var(--fhc-dark, #212529);
|
||||
|
||||
--fhc-calendar-radius-day-event: var(--fhc-calendar-radius-event);
|
||||
--fhc-calendar-radius-week-event: var(--fhc-calendar-radius-event);
|
||||
--fhc-calendar-radius-month-event: 0;
|
||||
--fhc-calendar-radius-list-event: var(--fhc-calendar-radius-event);
|
||||
|
||||
.fhc-calendar-sm .fhc-calendar-month-page-day,
|
||||
.fhc-calendar-xs .fhc-calendar-month-page-day {
|
||||
position: relative;
|
||||
aspect-ratio: 1;
|
||||
--fhc-calendar-maxheight-allday: 7.125rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.fhc-calendar-past {
|
||||
background-color: var(--fhc-calendar-past);
|
||||
border-color: #E8E8E8;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.fhc-calendar-month-page-day-highlight {
|
||||
/*background-color: #f5f5f5;*/
|
||||
/*background-color: red;*/
|
||||
}
|
||||
.fhc-highlight-week {
|
||||
/*border-color: black !important;*/
|
||||
}
|
||||
|
||||
.fhc-highlight-day {
|
||||
box-shadow: inset 0 0 0 2px black !important;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.selectedEvent {
|
||||
background-color: #00649c !important;
|
||||
color: white;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
.background-past {
|
||||
background-color: var(--fhc-calendar-bg-markings-past);
|
||||
border-bottom: solid 1px var(--fhc-calendar-border-markings-past);
|
||||
}
|
||||
.background-past > span {
|
||||
display: none;
|
||||
}
|
||||
.background-past.bg-end > span {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 1rem;
|
||||
padding: 0 .5rem;
|
||||
background-color: var(--fhc-calendar-bg-markings-past-label);
|
||||
border: solid 1px var(--fhc-calendar-border-markings-past-label);
|
||||
font-size: .875em;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
.event {
|
||||
cursor: pointer;
|
||||
font-size: var(--fhc-calendar-fontsize-event, .875rem);
|
||||
}
|
||||
.fhc-calendar-mode-day .fhc-calendar-base-grid-line,
|
||||
.fhc-calendar-mode-week .fhc-calendar-base-grid-line {
|
||||
padding: 0 var(--fhc-calendar-gap-events, var(--fhc-calendar-gap, 1px));
|
||||
}
|
||||
.fhc-calendar-mode-day .all-day-events .event,
|
||||
.fhc-calendar-mode-week .all-day-events .event {
|
||||
margin: var(--fhc-calendar-gap-events, var(--fhc-calendar-gap, 1px)) 0;
|
||||
}
|
||||
.fhc-calendar-mode-list .event {
|
||||
margin-bottom: var(--fhc-calendar-gap-events, var(--fhc-calendar-gap, 1px));
|
||||
}
|
||||
/* Border */
|
||||
.fhc-calendar-mode-day .grid-main .event > *,
|
||||
.fhc-calendar-mode-week .grid-main .event > * {
|
||||
border: solid 1px var(--fhc-calendar-border-event);
|
||||
}
|
||||
.fhc-calendar-mode-day .event {
|
||||
--event-radius: var(--fhc-calendar-radius-day-event, 0);
|
||||
}
|
||||
.fhc-calendar-mode-week .event {
|
||||
--event-radius: var(--fhc-calendar-radius-week-event, 0);
|
||||
}
|
||||
.fhc-calendar-mode-month .event {
|
||||
--event-radius: var(--fhc-calendar-radius-month-event, 0);
|
||||
}
|
||||
.fhc-calendar-mode-list .event {
|
||||
--event-radius: var(--fhc-calendar-radius-list-event, 0);
|
||||
}
|
||||
.fhc-calendar-mode-day .all-day-events .event > *,
|
||||
.fhc-calendar-mode-week .all-day-events .event > *,
|
||||
.fhc-calendar-mode-day .event.event-begin > *,
|
||||
.fhc-calendar-mode-week .event.event-begin > * {
|
||||
border-top-left-radius: var(--event-radius);
|
||||
border-top-right-radius: var(--event-radius);
|
||||
}
|
||||
.fhc-calendar-mode-day .all-day-events .event > *,
|
||||
.fhc-calendar-mode-week .all-day-events .event > *,
|
||||
.fhc-calendar-mode-day .event.event-end > *,
|
||||
.fhc-calendar-mode-week .event.event-end > * {
|
||||
border-bottom-left-radius: var(--event-radius);
|
||||
border-bottom-right-radius: var(--event-radius);
|
||||
}
|
||||
.fhc-calendar-mode-month .event.event-begin > * {
|
||||
border-top-left-radius: var(--event-radius);
|
||||
border-bottom-left-radius: var(--event-radius);
|
||||
}
|
||||
.fhc-calendar-mode-month .event.event-end > * {
|
||||
border-top-right-radius: var(--event-radius);
|
||||
border-bottom-right-radius: var(--event-radius);
|
||||
}
|
||||
.fhc-calendar-mode-list .event > * {
|
||||
border-radius: var(--event-radius);
|
||||
}
|
||||
/* Colors */
|
||||
.fhc-calendar-base .event {
|
||||
--event-default-bg: var(--fhc-calendar-bg-event, #ccc);
|
||||
}
|
||||
.fhc-calendar-base .event .event-type-reservierung {
|
||||
--event-default-bg: var(--fhc-calendar-bg-reservierung, #fff);
|
||||
}
|
||||
.fhc-calendar-base .event > * {
|
||||
background-color: var(--event-bg, var(--event-default-bg));
|
||||
color: var(--event-color, var(--fhc-calendar-color-event, #000));
|
||||
}
|
||||
.fhc-calendar-base .event > *:hover {
|
||||
filter: brightness(120%);
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/* Labels */
|
||||
/* ====== */
|
||||
.fhc-calendar-base-label-dow .short,
|
||||
.fhc-calendar-base-label-dow .narrow,
|
||||
.fhc-calendar-base-label-day .full,
|
||||
.fhc-calendar-base-label-day .short,
|
||||
.fhc-calendar-base-label-day .narrow,
|
||||
.fhc-calendar-mode-month .fhc-calendar-base-label-day .long,
|
||||
.collapsed-header .fhc-calendar-base-label-day .long,
|
||||
.collapsed-header .fhc-calendar-base-label-dow .long {
|
||||
display: none;
|
||||
}
|
||||
.fhc-calendar-mode-month .fhc-calendar-base-label-day .narrow,
|
||||
.collapsed-header .fhc-calendar-base-label-day .short,
|
||||
.collapsed-header .fhc-calendar-base-label-dow .short {
|
||||
display: block;
|
||||
}
|
||||
.fhc-calendar-mode-day .fhc-calendar-base-label-day,
|
||||
.fhc-calendar-mode-week .fhc-calendar-base-label-day {
|
||||
font-size: .875em;
|
||||
}
|
||||
.fhc-calendar-mode-day .main-header,
|
||||
.fhc-calendar-mode-week .main-header {
|
||||
text-align: center;
|
||||
}
|
||||
.fhc-calendar-mode-month .fhc-calendar-base-label-week {
|
||||
text-align: right;
|
||||
}
|
||||
.fhc-calendar-base-label-week {
|
||||
margin-left: .5rem;
|
||||
}
|
||||
.fhc-calendar-base-label-time,
|
||||
.fhc-calendar-base-label-day,
|
||||
.fhc-calendar-base-label-week {
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
.fhc-calendar-mode-day .fhc-calendar-base-label-dow,
|
||||
.fhc-calendar-mode-week .fhc-calendar-base-label-dow,
|
||||
.fhc-calendar-base-label-day,
|
||||
.fhc-calendar-base-label-week {
|
||||
cursor: pointer;
|
||||
}
|
||||
.fhc-calendar-base-label-week span + span:before {
|
||||
content: "/";
|
||||
}
|
||||
.fhc-calendar-mode-month .fhc-calendar-base-grid-line-event.event-header {
|
||||
height: 100%;
|
||||
}
|
||||
.fhc-calendar-mode-month .fhc-calendar-base-label-day {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.fhc-calendar-mode-month .fhc-calendar-base-grid-line-event.event-header {
|
||||
--event-bg: transparent;
|
||||
font-weight: bold;
|
||||
font-size: var(--fhc-calendar-fontsize-month-day, 1rem);
|
||||
}
|
||||
.fhc-calendar-mode-month .fhc-calendar-base-grid-line-event.event-header .disabled {
|
||||
opacity: .3;
|
||||
}
|
||||
|
||||
|
||||
/* Base Grid */
|
||||
/* ========= */
|
||||
/* Grid */
|
||||
.fhc-calendar-base-grid {
|
||||
--fhc-calendar-axis-collapsible: .2fr;
|
||||
background-color: var(--fhc-calendar-bg-gap);
|
||||
grid-gap: var(--fhc-calendar-gap-header, calc(var(--fhc-calendar-gap, 1px) * 2));
|
||||
}
|
||||
.fhc-calendar-base-grid .grid-header {
|
||||
background-color: var(--fhc-calendar-bg-header-gap, var(--fhc-calendar-bg-gap));
|
||||
}
|
||||
.fhc-calendar-base-grid .grid-header:before,
|
||||
.fhc-calendar-base-grid .grid-allday:before {
|
||||
content: "";
|
||||
}
|
||||
.fhc-calendar-base-grid .grid-header:before,
|
||||
.fhc-calendar-base-grid .grid-header .main-header {
|
||||
background-color: var(--fhc-calendar-bg-header);
|
||||
}
|
||||
.fhc-calendar-base-grid .grid-allday {
|
||||
background-color: var(--fhc-calendar-bg-allday-gap, var(--fhc-calendar-bg-gap));
|
||||
}
|
||||
.fhc-calendar-base-grid .grid-allday:before,
|
||||
.fhc-calendar-base-grid .grid-allday .all-day-events {
|
||||
background-color: var(--fhc-calendar-bg-allday);
|
||||
}
|
||||
.fhc-calendar-base-grid .grid-allday .all-day-events {
|
||||
max-height: var(--fhc-calendar-maxheight-allday);
|
||||
overflow-y: auto;
|
||||
}
|
||||
.fhc-calendar-base-grid .grid-main {
|
||||
grid-gap: var(--fhc-calendar-gap, 1px);
|
||||
background-color: var(--fhc-calendar-bg-body-gap, var(--fhc-calendar-bg-gap));
|
||||
}
|
||||
.fhc-calendar-base-grid .grid-main .part-header,
|
||||
.fhc-calendar-base-grid .grid-main .part-body {
|
||||
background-color: var(--fhc-calendar-bg-body);
|
||||
}
|
||||
.fhc-calendar-base-grid-line {
|
||||
grid-gap: var(--fhc-calendar-gap-events, var(--fhc-calendar-gap, 1px));
|
||||
}
|
||||
.fhc-calendar-base-label-time {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: end;
|
||||
}
|
||||
/* Header */
|
||||
.fhc-calendar-base-header .header-picker {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.fhc-calendar-base-header .header-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
margin-bottom: .5rem;
|
||||
row-gap: .5rem;
|
||||
}
|
||||
.fhc-calendar-base-header .header-userdefined:empty {
|
||||
display: none;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.fhc-calendar-base-header {
|
||||
display: grid;
|
||||
grid-template-columns: [userdefined] 1fr [picker] max-content [modes] 1fr;
|
||||
align-items: center;
|
||||
}
|
||||
.fhc-calendar-base-header .header-actions {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 1;
|
||||
display: grid;
|
||||
grid-template-columns: subgrid;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.fhc-calendar-base-header .header-userdefined {
|
||||
grid-column: userdefined;
|
||||
}
|
||||
.fhc-calendar-base-header .header-modes {
|
||||
grid-column: modes;
|
||||
}
|
||||
.fhc-calendar-base-header .header-picker {
|
||||
grid-column: picker;
|
||||
grid-row: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Modes */
|
||||
/* ===== */
|
||||
/* Day */
|
||||
.fhc-calendar-mode-day .event-details {
|
||||
padding: 1.5rem;
|
||||
width: 100%;
|
||||
border-left: solid 1px var(--fhc-calendar-bg-gap);
|
||||
}
|
||||
.fhc-calendar-mode-day .today {
|
||||
background-color: var(--fhc-calendar-bg-day-today);
|
||||
color: var(--fhc-calendar-color-day-today);
|
||||
}
|
||||
/* Week */
|
||||
.fhc-calendar-mode-week .today {
|
||||
background-color: var(--fhc-calendar-bg-week-today);
|
||||
color: var(--fhc-calendar-color-week-today);
|
||||
}
|
||||
/* Month */
|
||||
.fhc-calendar-mode-month .fhc-calendar-base-grid-line {
|
||||
overflow: hidden;
|
||||
line-height: 1.8;
|
||||
grid-template-rows: 2em;
|
||||
grid-auto-rows: 1.8em;
|
||||
font-size: var(--fhc-calendar-fontsize-event, .875rem);
|
||||
}
|
||||
.fhc-calendar-mode-month .fhc-calendar-base-grid-line-event {
|
||||
font-size: 1em;
|
||||
height: 1.8em;
|
||||
overflow: hidden;
|
||||
}
|
||||
.fhc-calendar-mode-month .fhc-calendar-base-grid-line-event .placeholder-glow {
|
||||
text-indent: 0;
|
||||
}
|
||||
.fhc-calendar-mode-month .fhc-calendar-base-grid-line-event * {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.fhc-calendar-mode-month .event > .today {
|
||||
background-color: var(--fhc-calendar-bg-month-today);
|
||||
color: var(--fhc-calendar-color-month-today);
|
||||
}
|
||||
/* List */
|
||||
.fhc-calendar-mode-list .today {
|
||||
background-color: var(--fhc-calendar-bg-list-today);
|
||||
color: var(--fhc-calendar-color-list-today);
|
||||
}
|
||||
|
||||
/* Overlays */
|
||||
/* ======== */
|
||||
/* No Events */
|
||||
.fhc-calendar-no-events-overlay {
|
||||
background: linear-gradient(120deg, var(--fhc-calendar-bg-overlay1), var(--fhc-calendar-bg-overlay2));
|
||||
opacity: .7;
|
||||
text-align: center;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.fhc-calendar-no-events-overlay:before {
|
||||
content: "";
|
||||
display: block;
|
||||
height: 100px;
|
||||
}
|
||||
@@ -1,5 +1,43 @@
|
||||
@import './calendar.css';
|
||||
@import './dashboard/news.css';
|
||||
@import './dashboard/LvPlan.css';
|
||||
|
||||
:root{
|
||||
--fhc-dashboard-danger: var(--fhc-danger, #842029);
|
||||
--fhc-dashboard-grid-size: 4;
|
||||
--fhc-dashboard-link: var(--fhc-link, #0a57ca);
|
||||
--fhc-dashboard-gridrow-background: var(--fhc-background, white);
|
||||
--fhc-dashboard-gridrow-background-highlight: var(--fhc-background-highlight, #6c757d);
|
||||
--fhc-dashboard-draggeditem-background: var(--fhc-background, white);
|
||||
--fhc-dashboard-item-overlay-background: var(--fhc-background-highlight, rgba(0, 100, 156, 1));
|
||||
--fhc-dashboard-item-overly-border-color: var(--fhc-border, lightgray);
|
||||
--fhc-dashboard-denied-dragging-animation-color: var(--fhc-danger, red);
|
||||
--fhc-dashboard-pin-pinned-hover-color: var(--fhc-fifth-highlight, rgb(153, 116, 116));
|
||||
--fhc-dashboard-section-info-color: var(--fhc-primary, #00649c);
|
||||
--fhc-dashboard-section-info-color-hover: var(--fhc-primary-highlight, #005585);
|
||||
}
|
||||
|
||||
@media(max-width: 577px) {
|
||||
:root {
|
||||
--fhc-dashboard-grid-size: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.core-dashboard a{
|
||||
color: var(--fhc-dashboard-link);
|
||||
}
|
||||
|
||||
@media (max-width: 576px){
|
||||
.widget-icon {
|
||||
max-height: 250px;
|
||||
object-fit: cover;
|
||||
}
|
||||
.widget-icon-container{
|
||||
max-width: 250px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-tile-hover {
|
||||
height: 100%;
|
||||
@@ -11,18 +49,59 @@
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.dashboard-section > .newGridRow{
|
||||
position:absolute;
|
||||
width:20px;
|
||||
height:20px;
|
||||
padding:0;
|
||||
bottom:0;
|
||||
left:50%;
|
||||
transform:translate(-50%, 50%);
|
||||
background-color: var(--fhc-dashboard-gridrow-background);
|
||||
}
|
||||
|
||||
.newGridRow:hover {
|
||||
color:white;
|
||||
background-color: var(--fhc-dashboard-gridrow-background-highlight);
|
||||
}
|
||||
|
||||
.empty-tile-hover:hover {
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-500 -500 1448 1512"><path fill="rgb(211, 211, 211)" d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zM200 344V280H136c-13.3 0-24-10.7-24-24s10.7-24 24-24h64V168c0-13.3 10.7-24 24-24s24 10.7 24 24v64h64c13.3 0 24 10.7 24 24s-10.7 24-24 24H248v64c0 13.3-10.7 24-24 24s-24-10.7-24-24z"/></svg>');
|
||||
}
|
||||
|
||||
.alert-danger .form-check-input:checked {
|
||||
border-color: #842029;
|
||||
background-color: #842029;
|
||||
border-color: var(--fhc-dashboard-danger);
|
||||
background-color: var(--fhc-dashboard-danger);
|
||||
}
|
||||
|
||||
:root {
|
||||
--fhc-dashboard-grid-size: 4;
|
||||
}
|
||||
|
||||
@media(max-width: 1400px) {
|
||||
:root {
|
||||
--fhc-dashboard-grid-size: 4;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: 1200px) {
|
||||
:root {
|
||||
--fhc-dashboard-grid-size: 3;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: 992px) {
|
||||
:root {
|
||||
--fhc-dashboard-grid-size: 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: 768px) {
|
||||
:root {
|
||||
--fhc-dashboard-grid-size: 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: 577px) {
|
||||
:root {
|
||||
--fhc-dashboard-grid-size: 1;
|
||||
@@ -34,11 +113,93 @@
|
||||
}
|
||||
|
||||
.cursor-nw-resize {
|
||||
cursor: nw-resize;
|
||||
cursor: nw-resize !important;
|
||||
}
|
||||
|
||||
.cursor-ew-resize {
|
||||
cursor: ew-resize !important;
|
||||
}
|
||||
|
||||
.cursor-ns-resize {
|
||||
cursor: ns-resize !important;
|
||||
}
|
||||
|
||||
.no-drag-drop {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
.cursor-move {
|
||||
cursor: move;
|
||||
cursor: move !important;
|
||||
}
|
||||
|
||||
.draggedItem {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: var(--fhc-dashboard-draggeditem-background);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dashboard-item-overlay{
|
||||
background-color: var(--fhc-dashboard-item-overlay-background);
|
||||
}
|
||||
|
||||
.dashboard-item-overlay::before{
|
||||
position:absolute;
|
||||
content:"";
|
||||
top:0.25rem;
|
||||
left:0.25rem;
|
||||
right:0.25rem;
|
||||
bottom:0.25rem;
|
||||
border:4px dashed var(--fhc-dashboard-item-overly-border-color);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
#deleteBookmark i{
|
||||
color: var(--fhc-dashboard-danger);
|
||||
}
|
||||
|
||||
.pin:hover{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pin[pinned]:hover{
|
||||
color: var(--fhc-dashboard-pin-pinned-hover-color);
|
||||
}
|
||||
|
||||
.section-info{
|
||||
color: var(--fhc-dashboard-section-info-color);
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.section-info:hover {
|
||||
color: var(--fhc-dashboard-section-info-color-hover);
|
||||
}
|
||||
|
||||
.denied-dragging-animation {
|
||||
animation: wiggle 0.5s linear;
|
||||
color: var(--fhc-dashboard-denied-dragging-animation-color) !important;
|
||||
}
|
||||
|
||||
@keyframes wiggle {
|
||||
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: rotate(-30deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: rotate(30deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(0em);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
.dashboard-widget-lvplan .fhc-calendar-base .card {
|
||||
--bs-card-cap-padding-x: 0;
|
||||
--bs-card-cap-padding-y: 0;
|
||||
--bs-card-border-radius: 0;
|
||||
--bs-card-border-width: 0;
|
||||
--dp-input-padding: .375rem .75rem;
|
||||
--dp-input-icon-padding: calc(1rem + 24px);
|
||||
}
|
||||
.dashboard-widget-lvplan .fhc-calendar-base .card-header {
|
||||
border-bottom-width: var(--bs-border-width);
|
||||
}
|
||||
.dashboard-widget-lvplan .fhc-calendar-base .card-header > .row > .col-auto,
|
||||
.dashboard-widget-lvplan .fhc-calendar-base .card-header .btn-group {
|
||||
width: 100%;
|
||||
}
|
||||
.dashboard-widget-lvplan .fhc-calendar-base .card-header input {
|
||||
--dp-font-size: .7em;
|
||||
}
|
||||
.dashboard-widget-lvplan .fhc-calendar-base .card-header .btn {
|
||||
--bs-btn-font-size: .7em;
|
||||
}
|
||||
.dashboard-widget-lvplan .fhc-calendar-base-label-day {
|
||||
cursor: initial;
|
||||
}
|
||||
.dashboard-widget-lvplan .fhc-calendar-mode-list-view {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
:root{
|
||||
--news-widget-height: 1;
|
||||
--fhc-news-widget-height: 1;
|
||||
--fhc-news-widget-bg: var(--fhc-primary);
|
||||
--fhc-news-widget-highlight-color: var(--fhc-primary-highlight);
|
||||
--fhc-news-widget-border: var(--fhc-border);
|
||||
--fhc-news-widget-border-highlight: var(--fhc-border-highlight);
|
||||
--fhc-news-widget-text-light: var(--fhc-light);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,28 +13,68 @@
|
||||
@media (min-width :576px) {
|
||||
.fhc-news-menu-item {
|
||||
/* padding: top right bottom left */
|
||||
padding:0em 0em 0em 0em !important;
|
||||
padding:0.125em 0.125em 0.125em 0.125em !important;
|
||||
max-height:30px !important;
|
||||
}
|
||||
|
||||
.fhc-news-menu-item-betreff p {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
line-clamp: 1;
|
||||
/* Number of lines to show */
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
/* media query for md sizes */
|
||||
@media (min-width :768px) {
|
||||
.fhc-news-menu-item {
|
||||
/* padding: top right bottom left */
|
||||
padding: 0.3em 0.3em 0.3em 0.3em !important;
|
||||
padding: 0.25em 0.25em 0.25em 0.25em !important;
|
||||
max-height: 50px !important;
|
||||
}
|
||||
|
||||
.fhc-news-menu-item-betreff p {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
line-clamp: 1;
|
||||
/* Number of lines to show */
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
/* media query for lg sizes */
|
||||
@media (min-width :992px) {
|
||||
.fhc-news-menu-item {
|
||||
/* padding: top right bottom left */
|
||||
padding: 0.3em 0.3em 0.3em 0.3em !important;
|
||||
padding: 0.25em 0.25em 0.25em 0.25em !important;
|
||||
max-height: 75px !important;
|
||||
}
|
||||
|
||||
.fhc-news-menu-item-betreff p {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
/* Number of lines to show */
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
/* media query for xl sizes */
|
||||
@media (min-width :1200px) {
|
||||
.fhc-news-menu-item {
|
||||
/* padding: top right bottom left */
|
||||
padding: 1em 1em 0.3em 1em !important;
|
||||
padding: 1em 0.25em 0.25em 0.25em !important;
|
||||
height: 100px !important;
|
||||
}
|
||||
|
||||
.fhc-news-menu-item-betreff p {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
/* Number of lines to show */
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,42 +82,51 @@
|
||||
@media (min-width :1400px) {
|
||||
.fhc-news-menu-item {
|
||||
/* padding: top right bottom left */
|
||||
padding: 1.2em 1.2em 0.3em 1.2em !important;
|
||||
padding: 1.2em 0.5em 0.5em 0.5em !important;
|
||||
height: 125px !important;
|
||||
}
|
||||
|
||||
.fhc-news-menu-item-betreff p {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
/* Number of lines to show */
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.widgets-news{
|
||||
padding: .5rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.widgets-news img
|
||||
{
|
||||
max-width: 100%;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.fhc-news-menu-item {
|
||||
padding: 0.375rem;
|
||||
color: var(--fhc-cis-menu-lvl-1-color);
|
||||
min-height: 5%;
|
||||
max-height: 30%;
|
||||
color: var(--fhc-news-widget-text-light);
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #00649c;
|
||||
border: 1px solid #f1f1f1;
|
||||
background-color: var(--fhc-news-widget-bg);
|
||||
border: 1px solid var(--fhc-news-widget-border);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease, border-color 0.2s ease; /* Smooth transition */
|
||||
}
|
||||
|
||||
.fhc-news-menu-item:hover {
|
||||
background-color: var(--fhc-cis-menu-lvl-1-bg-hover);
|
||||
border-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.fhc-news-menu-item:active {
|
||||
background-color: var(--fhc-cis-menu-lvl-1-color-hover);
|
||||
border-color: #f1f1f1;
|
||||
background-color: var(--fhc-news-widget-highlight-color);
|
||||
border-color: var(--fhc-news-widget-border-highlight);
|
||||
}
|
||||
|
||||
.fhc-news-menu-item.selected {
|
||||
background-color: var(--fhc-cis-menu-lvl-1-bg-hover);
|
||||
border-right: 2px solid #fff;
|
||||
background-color: var(--fhc-news-widget-highlight-color);
|
||||
border: 2px solid var(--fhc-news-widget-border-highlight);
|
||||
|
||||
}
|
||||
|
||||
@@ -83,7 +137,6 @@
|
||||
.fhc-news-menu-item-betreff
|
||||
{
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -111,32 +164,32 @@
|
||||
|
||||
.carousel-control-prev,
|
||||
.carousel-control-next {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
top: 0.5rem !important;
|
||||
bottom: auto !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* Show the buttons when the carousel is hovered */
|
||||
.carousel:hover .carousel-control-prev,
|
||||
.carousel:hover .carousel-control-next {
|
||||
opacity: 1;
|
||||
|
||||
.fhc-news-items-lg .row .news-truncate span{
|
||||
color: var(--fhc-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fhc-news-text{
|
||||
font-size: 0.75em;
|
||||
.fhc-news-items-lg .row:hover .news-truncate span{
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.fhc-news-xs {
|
||||
font-size: 0.6em;
|
||||
|
||||
|
||||
.news-truncate {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
/* number of lines to show */
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.fhc-news-sm {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
.fhc-news-md {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.fhc-news-lg {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
@@ -5,28 +5,73 @@
|
||||
|
||||
@import url(./switches.css);
|
||||
|
||||
#search-filter{
|
||||
border: 1px solid #ced4da;
|
||||
:root{
|
||||
--fhc-searchbar-shadow: var(--fhc-shadow-40, #ced4da);
|
||||
--fhc-searchbar-settings-background-color: var(--fhc-dark, #343a40);
|
||||
--fhc-searchbar-results-background-color: var(--fhc-background, #fff);
|
||||
--fhc-searchbar-results-border-color: var(--fhc-border, lightgrey);
|
||||
--fhc-searchbar-results-link-color: var(--fhc-link, #0d6efd);
|
||||
--fhc-searchbar-results-btn-color: var(--bs-btn-color, white);
|
||||
}
|
||||
|
||||
.searchbar_input_clear {
|
||||
/* 1rem * 5 / 8 = width of icon */
|
||||
margin-inline-start: calc(-1rem * 5 / 8 - var(--bs-btn-padding-x) * 2 - var(--bs-border-width)) !important;
|
||||
border: 0;
|
||||
margin-inline-end: 1px;
|
||||
z-index: 1;
|
||||
}
|
||||
.searchbar_input_clear:hover,
|
||||
.searchbar_input_clear:focus {
|
||||
color: var(--bs-body-color);
|
||||
background-color: transparent;
|
||||
}
|
||||
.searchbar_input:focus,
|
||||
.searchbar_input:focus ~ .searchbar_input_clear {
|
||||
z-index: 5; /* same as bootstrap .input-group > .form-control:focus */
|
||||
}
|
||||
|
||||
.searchbar_setting_btn {
|
||||
--bs-btn-color: var(--bs-body-color);
|
||||
--bs-btn-bg: var(--bs-tertiary-bg);
|
||||
--bs-btn-border-color: var(--bs-border-color);
|
||||
}
|
||||
|
||||
|
||||
.searchbar_searchbox.open {
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
|
||||
.searchbar_settings {
|
||||
position: absolute;
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
background-color: #343a40;
|
||||
background-color: var(--fhc-searchbar-settings-background-color);
|
||||
}
|
||||
|
||||
.searchbar_results {
|
||||
position: absolute;
|
||||
z-index: 9998;
|
||||
background-color: #fff;
|
||||
border: 1px solid lightgrey;
|
||||
background-color: var(--fhc-searchbar-results-background-color);
|
||||
border: 1px solid var(--fhc-searchbar-results-border-color);
|
||||
padding: .5rem;
|
||||
top: 100%;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, .4);
|
||||
box-shadow: 2px 2px 4px var(--fhc-searchbar-shadow);
|
||||
}
|
||||
|
||||
.searchbar_results a{
|
||||
color: var(--fhc-searchbar-results-link-color);
|
||||
}
|
||||
/*
|
||||
.searchbar_results a:not(a~i) {
|
||||
color: inherit;
|
||||
}
|
||||
*/
|
||||
.searchbar_results a.btn {
|
||||
color: var(--fhc-searchbar-results-btn-color);
|
||||
}
|
||||
|
||||
.searchbar_results_scroller {
|
||||
@@ -35,7 +80,7 @@
|
||||
}
|
||||
|
||||
.searchbar_result {
|
||||
border-bottom: 1px solid lightgrey;
|
||||
border-bottom: 1px solid var(--fhc-searchbar-results-border-color);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@@ -104,6 +149,76 @@
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
/* new variant with template/frame */
|
||||
|
||||
.searchbar-result-template-frame {
|
||||
border-bottom: 1px solid lightgrey;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.searchbar-result-template-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: .5rem;
|
||||
padding: 0;
|
||||
margin: 1rem 0 0;
|
||||
}
|
||||
|
||||
.searchbar-square-image {
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 0;
|
||||
padding-bottom: 100%;
|
||||
}
|
||||
.searchbar-square-image > * {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.searchbar-square-image > img {
|
||||
object-fit: cover;
|
||||
}
|
||||
.searchbar-square-image > div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
color: rgba(var(--bs-white-rgb),1);
|
||||
background-color: rgba(var(--bs-primary-rgb), 1);
|
||||
}
|
||||
.searchbar-square-image.rounded-circle > img,
|
||||
.searchbar-square-image.rounded-circle > div {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.searchbar-rounded-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.searchbar-rounded-image > img {
|
||||
width: 100%;
|
||||
}
|
||||
.searchbar-rounded-image > div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
color: rgba(var(--bs-white-rgb),1);
|
||||
background-color: rgba(var(--bs-primary-rgb), 1);
|
||||
height: calc((100px - .75em) * 1.3);
|
||||
}
|
||||
.searchbar-rounded-image > img,
|
||||
.searchbar-rounded-image > div {
|
||||
border-radius: .25rem;
|
||||
}
|
||||
|
||||
.no-margin-paragraphs p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.non-selectable {
|
||||
user-select: none;
|
||||
-webkit-user-select: none; /* Safari */
|
||||
@@ -113,4 +228,4 @@
|
||||
|
||||
.searchbar_inaktiv {
|
||||
opacity: .6;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
:root{
|
||||
--fhc-switches-checked-bg: var(--fhc-light, white);
|
||||
--fhc-switches-unchecked-bg: var(--fhc-disabled, var(--bs-gray-600));
|
||||
--fhc-switches-shadow: var(--fhc-shadow-20, rgba(108, 117, 125,.25));
|
||||
}
|
||||
|
||||
.fhc-switches:checked {
|
||||
background-color: white !important;
|
||||
border-color: white !important;
|
||||
background-color: var(--fhc-switches-checked-bg) !important;
|
||||
border-color: transparent;
|
||||
background-position: right center !important;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23000000'/%3e%3c/svg%3e") !important;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='black'/%3e%3c/svg%3e") !important;
|
||||
}
|
||||
|
||||
.fhc-switches {
|
||||
background-color: var(--bs-gray-600) !important;
|
||||
border-color: var(--bs-gray-600) !important;
|
||||
background-color: var(--fhc-switches-unchecked-bg) !important;
|
||||
border-color: transparent;
|
||||
background-position: left center !important;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%238e99a3'/%3e%3c/svg%3e") !important;
|
||||
}
|
||||
|
||||
.fhc-switches:focus {
|
||||
box-shadow: 0 0 0 .25rem rgba(108, 117, 125,.25);
|
||||
box-shadow: 0 0 0 .25rem var(--fhc-switches-shadow);
|
||||
}
|
||||
|
||||
/*.form-switch .form-check-input:checked {
|
||||
|
||||
|
||||
rgb(52, 58, 64)*/
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
Author : bambi
|
||||
*/
|
||||
:root {
|
||||
--fhc-verticalsplit-vsplitter-bg-color: #eee;
|
||||
--fhc-verticalsplit-vsplitter-border-color: #eee;
|
||||
--fhc-verticalsplit-vsplitter-splitactions-color: #000;
|
||||
--fhc-verticalsplit-vsplitter-bg-color: var(--fhc-background, #eee);
|
||||
--fhc-verticalsplit-vsplitter-border-color: var(--fhc-border, #eee);
|
||||
--fhc-verticalsplit-vsplitter-splitactions-color: var(--fhc-dark, #000);
|
||||
}
|
||||
|
||||
.verticalsplitted {
|
||||
|
||||
@@ -43,4 +43,12 @@
|
||||
--dp-border-color-hover: var(--bs-success);
|
||||
}
|
||||
.form-control.is-invalid ~ .dp__clear_icon,
|
||||
.was-validated .form-control:invalid ~ .dp__clear_icon { margin-right: calc(1.5em + .75rem - 12px) }
|
||||
.was-validated .form-control:invalid ~ .dp__clear_icon {
|
||||
margin-right: calc(1.5em + .75rem - 12px);
|
||||
}
|
||||
.p-component .dp__input_icons {
|
||||
box-sizing: content-box !important;
|
||||
}
|
||||
.input-group .dp__input_icons {
|
||||
z-index: 3;
|
||||
}
|
||||
@@ -24,46 +24,6 @@
|
||||
border-color: #DEE2E6 !important;
|
||||
}
|
||||
|
||||
.bg-info-subtle{
|
||||
background-color: #D9EDF7 !important;
|
||||
color: #31708F !important;
|
||||
border-style: solid !important;
|
||||
border-width: 1px !important;
|
||||
border-color: #BCE8F1 !important;
|
||||
}
|
||||
|
||||
.bg-warning-subtle{
|
||||
background-color: #FFF3CD !important;
|
||||
color: #664D03 !important;
|
||||
border-style: solid !important;
|
||||
border-width: 1px !important;
|
||||
border-color: #FFE69C !important;
|
||||
}
|
||||
|
||||
.bg-success-subtle{
|
||||
background-color: #D1E7DD !important;
|
||||
color: #0A3622 !important;
|
||||
border-style: solid !important;
|
||||
border-width: 1px !important;
|
||||
border-color: #A3CFBB !important;
|
||||
}
|
||||
|
||||
.bg-danger-subtle{
|
||||
background-color: #F8D7DA !important;
|
||||
color: #672B31 !important;
|
||||
border-style: solid !important;
|
||||
border-width: 1px !important;
|
||||
border-color: #F1AEB5 !important;
|
||||
}
|
||||
|
||||
.bg-primary-subtle{
|
||||
background-color: #CFE2FF !important;
|
||||
color: #052C65 !important;
|
||||
border-style: solid !important;
|
||||
border-width: 1px !important;
|
||||
border-color: #9EC5FE !important;
|
||||
}
|
||||
|
||||
|
||||
/* html attribute disabled and title don't work together, sets the pointer-event to auto in order to see the title toolip */
|
||||
.btn:disabled {
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
/* css overrides for static cms content */
|
||||
@import './default_cms_fixes.css';
|
||||
|
||||
/* colors */
|
||||
:root {
|
||||
|
||||
--fhc-black-10: black;
|
||||
--fhc-black-20: #1d2024;
|
||||
--fhc-black-30: #212529;
|
||||
--fhc-black-40: #292b2a;
|
||||
--fhc-black-50: #2B3035;
|
||||
--fhc-black-60: #2c3136;
|
||||
--fhc-black-70: #343a40;
|
||||
|
||||
--fhc-white-10: #FFFFFF;
|
||||
--fhc-white-20: #f6f6f6;
|
||||
--fhc-white-30: #eceff1;
|
||||
--fhc-white-40: #e9ecef;
|
||||
--fhc-white-50: #dee2e6;
|
||||
--fhc-white-60: #d5dae0;
|
||||
--fhc-white-70: #ced4da;
|
||||
--fhc-white-80: #c7cbcf;
|
||||
--fhc-white-90: #aeb3b8;
|
||||
|
||||
--fhc-gray-10: #858f97;
|
||||
--fhc-gray-20: #72777A;
|
||||
--fhc-gray-30: #616568;
|
||||
--fhc-gray-40: #495057;
|
||||
|
||||
--fhc-blue-10: #0a57ca;
|
||||
--fhc-blue-20: #79b7d8;
|
||||
/* --fhc-blue-20: #00649c;*/
|
||||
--fhc-blue-30: #00598d;
|
||||
/*--fhc-blue-40: #005585;*/
|
||||
--fhc-blue-40: #61aed8;
|
||||
--fhc-blue-50: #00507e;
|
||||
|
||||
--fhc-blue-primary: #006095;
|
||||
--fhc-blue-primary-highlight: #0086CB;
|
||||
|
||||
--fhc-red-10: #842029;
|
||||
--fhc-red-20: #800000;
|
||||
|
||||
--fhc-green-10: #008000;
|
||||
|
||||
--fhc-beige-10: rgba(245, 233, 215, 0.5);
|
||||
--fhc-beige-20: rgba(172, 153, 125, 0.5);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
:root.light {
|
||||
|
||||
|
||||
/* fhc base colors */
|
||||
--fhc-primary: var(--fhc-blue-primary);
|
||||
--fhc-primary-highlight: var(--fhc-blue-primary-highlight);
|
||||
|
||||
--fhc-secondary: var(--fhc-white-20);
|
||||
--fhc-secondary-highlight: var(--fhc-white-40);
|
||||
|
||||
--fhc-tertiary: var(--fhc-white-40);
|
||||
--fhc-tertiary-highlight: var(--fhc-white-60);
|
||||
|
||||
--fhc-fourth: var(--fhc-white-60);
|
||||
--fhc-fourth-highlight: var(--fhc-white-80);
|
||||
|
||||
--fhc-fifth: var(--fhc-white-80);
|
||||
--fhc-fifth-highlight: var(--fhc-white-90);
|
||||
|
||||
/*background*/
|
||||
--fhc-background: var(--fhc-white-10);
|
||||
--fhc-background-highlight: var(--fhc-white-60);
|
||||
|
||||
/*text*/
|
||||
--fhc-text: var(--fhc-black-30);
|
||||
|
||||
/* shadows and borders */
|
||||
--fhc-shadow-20: rgba(0, 0, 0, 0.2);
|
||||
--fhc-shadow-40: rgba(0, 0, 0, 0.4);
|
||||
--fhc-shadow-60: rgba(0, 0, 0, 0.6);
|
||||
--fhc-shadow-80: rgba(0, 0, 0, 0.8);
|
||||
--fhc-box-shadow: var(--fhc-white-50);
|
||||
--fhc-border: var(--fhc-white-50);
|
||||
--fhc-border-highlight: var(--fhc-gray-40);
|
||||
|
||||
/* links */
|
||||
--fhc-link: var(--fhc-blue-10);
|
||||
|
||||
/* disabled */
|
||||
--fhc-disabled: var(--fhc-gray-10);
|
||||
|
||||
/* status colors */
|
||||
--fhc-danger: var(--fhc-red-10);
|
||||
--fhc-success: var(--fhc-green-10);
|
||||
--fhc-dark: var(--fhc-black-30);
|
||||
--fhc-light: var(--fhc-white-50);
|
||||
|
||||
--fhc-cis-menu-lvl-1-color: var(--fhc-white-50);
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
|
||||
/* fhc base colors */
|
||||
--fhc-primary: var(--fhc-blue-primary);
|
||||
--fhc-primary-highlight: var(--fhc-blue-primary-highlight);
|
||||
|
||||
--fhc-secondary: var(--fhc-black-50);
|
||||
--fhc-secondary-highlight: var(--fhc-black-70);
|
||||
|
||||
--fhc-tertiary: var(--fhc-black-70);
|
||||
--fhc-tertiary-highlight: var(--fhc-gray-40);
|
||||
|
||||
--fhc-danger: var(--fhc-red-10);
|
||||
|
||||
/*background*/
|
||||
--fhc-background: var(--fhc-black-30);
|
||||
--fhc-background-highlight: var(--fhc-black-20);
|
||||
|
||||
/*text*/
|
||||
--fhc-text: var(--fhc-white-50);
|
||||
|
||||
/* shadows and borders */
|
||||
--fhc-shadow-20: rgba(125, 125, 125, 0.2);
|
||||
--fhc-shadow-40: rgba(125, 125, 125, 0.4);
|
||||
--fhc-shadow-60: rgba(125, 125, 125, 0.6);
|
||||
--fhc-shadow-80: rgba(125, 125, 125, 0.8);
|
||||
--fhc-box-shadow: var(--fhc-gray-40);
|
||||
--fhc-border: var(--fhc-gray-40);
|
||||
--fhc-border-highlight: var(--fhc-white-90);
|
||||
|
||||
/* links */
|
||||
--fhc-link: var(--fhc-white-70);
|
||||
|
||||
/* disabled */
|
||||
--fhc-disabled: var(--fhc-gray-10);
|
||||
|
||||
/* status colors */
|
||||
--fhc-danger: var(--fhc-red-10);
|
||||
--fhc-success: var(--fhc-green-10);
|
||||
--fhc-dark: var(--fhc-black-50);
|
||||
--fhc-light: var(--fhc-white-70);
|
||||
|
||||
}
|
||||
|
||||
:root.purple {
|
||||
|
||||
--fhc-purple-primary: rgb(150, 120, 255);
|
||||
--fhc-purple-primary-highlight: rgb(170, 140, 255);
|
||||
--fhc-purple-5: rgb(245, 240, 255);
|
||||
--fhc-purple-10: rgb(235, 225, 255);
|
||||
--fhc-purple-20: rgb(220, 200, 255);
|
||||
--fhc-purple-30: rgb(200, 170, 255);
|
||||
--fhc-purple-40: rgb(180, 140, 240);
|
||||
--fhc-purple-50: rgb(160, 110, 225);
|
||||
--fhc-purple-60: rgb(140, 90, 210);
|
||||
--fhc-purple-70: rgb(120, 70, 195);
|
||||
--fhc-purple-80: rgb(100, 50, 180);
|
||||
--fhc-purple-90: rgb(80, 30, 165);
|
||||
|
||||
/* fhc base colors */
|
||||
--fhc-primary: var(--fhc-purple-primary-highlight);
|
||||
--fhc-primary-highlight: var(--fhc-purple-primary);
|
||||
|
||||
--fhc-secondary: var(--fhc-purple-20);
|
||||
--fhc-secondary-highlight: var(--fhc-purple-40);
|
||||
|
||||
--fhc-tertiary: var(--fhc-purple-30);
|
||||
--fhc-tertiary-highlight: var(--fhc-purple-50);
|
||||
|
||||
/* background */
|
||||
--fhc-background: var(--fhc-purple-5);
|
||||
--fhc-background-highlight: var(--fhc-purple-10);
|
||||
|
||||
/* text */
|
||||
--fhc-text: var(--fhc-black-20);
|
||||
|
||||
/* shadows and borders */
|
||||
--fhc-shadow-20: rgba(150, 100, 200, 0.2);
|
||||
--fhc-shadow-40: rgba(150, 100, 200, 0.4);
|
||||
--fhc-shadow-60: rgba(150, 100, 200, 0.6);
|
||||
--fhc-shadow-80: rgba(150, 100, 200, 0.8);
|
||||
--fhc-box-shadow: var(--fhc-white-50);
|
||||
--fhc-border: var(--fhc-white-50);
|
||||
--fhc-border-highlight: var(--fhc-gray-40);
|
||||
|
||||
/* links */
|
||||
--fhc-link: var(--fhc-black-50);
|
||||
|
||||
/* disabled */
|
||||
--fhc-disabled: var(--fhc-purple-20);
|
||||
|
||||
/* status colors */
|
||||
--fhc-danger: var(--fhc-red-10);
|
||||
--fhc-success: var(--fhc-green-10);
|
||||
--fhc-dark: var(--fhc-black-20);
|
||||
--fhc-light: var(--fhc-white-70);
|
||||
|
||||
}
|
||||
|
||||
/* fhc primary*/
|
||||
html.dark .bg-primary,
|
||||
html.dark .btn-primary,
|
||||
html.dark .btn-outline-primary {
|
||||
background-color: var(--fhc-primary) !important;
|
||||
border-color: var(--fhc-primary) !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
html.dark .btn-outline-primary:hover,
|
||||
html.dark .btn-primary:hover {
|
||||
filter: brightness(90%);
|
||||
}
|
||||
|
||||
html.dark .text-primary {
|
||||
color: var(--fhc-primary) !important;
|
||||
}
|
||||
|
||||
html.dark .btn-outline-primary,
|
||||
html.dark .border-primary {
|
||||
border-color: var(--fhc-primary) !important;
|
||||
}
|
||||
|
||||
/* fhc danger*/
|
||||
html.dark .bg-danger,
|
||||
html.dark .btn-danger,
|
||||
html.dark .btn-outline-danger {
|
||||
background-color: var(--fhc-danger) !important;
|
||||
border-color: var(--fhc-danger) !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
html.dark .btn-outline-danger:hover,
|
||||
html.dark .btn-danger:hover {
|
||||
filter: brightness(90%);
|
||||
}
|
||||
|
||||
html.dark .text-danger {
|
||||
color: var(--fhc-danger) !important;
|
||||
}
|
||||
|
||||
html.dark .border-danger {
|
||||
border-color: var(--fhc-danger) !important;
|
||||
}
|
||||
|
||||
|
||||
html.dark .tabulator,
|
||||
html.dark .tabulator .tabulator-header,
|
||||
html.dark .tabulator .tabulator-header .tabulator-col,
|
||||
html.dark .tabulator .tabulator-table,
|
||||
html.dark .tabulator .tabulator-row {
|
||||
background-color: var(--fhc-tertiary) !important;
|
||||
color: var(--fhc-text) !important;
|
||||
border-color: var(--fhc-border) !important;
|
||||
}
|
||||
|
||||
html.dark .tabulator .tabulator-cell {
|
||||
background-color: var(--fhc-tertiary) !important;
|
||||
border-color: var(--fhc-border) !important;
|
||||
}
|
||||
|
||||
html.dark .tabulator .tabulator-header .tabulator-col:hover {
|
||||
background-color: var(--fhc-tertiary) !important;
|
||||
}
|
||||
|
||||
.p-paginator{
|
||||
background-color: var(--fhc-background) !important;
|
||||
}
|
||||
|
||||
.p-paginator .p-paginator-pages .p-paginator-page.p-highlight,
|
||||
.p-dropdown-panel .p-dropdown-items .p-dropdown-item.p-highlight {
|
||||
background-color: var(--fhc-primary) !important;
|
||||
border-color: var(--fhc-primary) !important;
|
||||
color: var(--fhc-light) !important;
|
||||
}
|
||||
|
||||
.p-paginator .p-paginator-pages .p-paginator-page,
|
||||
.p-paginator .p-paginator-next,
|
||||
.p-paginator .p-paginator-prev,
|
||||
.p-paginator .p-paginator-first,
|
||||
.p-paginator .p-paginator-last {
|
||||
color: var(--fhc-primary) !important;
|
||||
}
|
||||
|
||||
.p-link:focus {
|
||||
box-shadow: 0 0 0 0.2rem rgba(var(--fhc-primary), 0.5) !important;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* css overrides for dark mode */
|
||||
html.dark #fhc-cms-content *[style*="color"] {
|
||||
color: var(--fhc-text) !important;
|
||||
}
|
||||
|
||||
/* fhc cms classes */
|
||||
html.dark #fhc-cms-content a {
|
||||
color: var(--fhc-link);
|
||||
}
|
||||
|
||||
html.dark #fhc-cms-content img {
|
||||
background-color: var(--fhc-text);
|
||||
color: var(--fhc-background);
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -95,10 +95,9 @@ var FHC_DialogLib = {
|
||||
*/
|
||||
_displayDialog: function(heading, message, icon, colorClass)
|
||||
{
|
||||
var html = "<p class='dialogmessage'><i class='glyphicon glyphicon-"+icon+"'></i> "+message+"</p>";
|
||||
var html = "<p class='dialogmessage'>"+message+"</p>";
|
||||
FHC_DialogLib.alertDefault(heading, html);
|
||||
$(".ui-dialog-titlebar").addClass(colorClass+" text-center");
|
||||
$(".glyphicon-"+icon).addClass(colorClass);
|
||||
$(".ui-dialog-titlebar").addClass(colorClass+" alert text-center");
|
||||
FHC_DialogLib._formatShortDialog();
|
||||
},
|
||||
|
||||
@@ -113,7 +112,6 @@ var FHC_DialogLib = {
|
||||
$(".ui-dialog-buttonpane.ui-widget-content").css("padding", ".3em .4em .5em .4em");
|
||||
$(".ui-dialog .ui-dialog-content").css("padding", "0");
|
||||
$(".ui-dialog-buttonset button").css("margin", "0");
|
||||
$(".dialogmessage i.glyphicon").css("background-color", "transparent");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -6,5 +6,10 @@ export default {
|
||||
{ }
|
||||
);
|
||||
},
|
||||
|
||||
getAuthInfo() {
|
||||
return this.$fhcApi.get(
|
||||
'/api/frontend/v1/AuthInfo/getAuthInfo',
|
||||
{}
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -21,5 +21,11 @@ export default {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/AuthInfo/getAuthUID'
|
||||
};
|
||||
},
|
||||
getAuthInfo() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/AuthInfo/getAuthInfo'
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (C) 2025 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export default {
|
||||
getAll() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/language/get'
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -18,49 +18,79 @@
|
||||
export default {
|
||||
getRoomInfo(ort_kurzbz, start_date, end_date) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Stundenplan/getRoomplan',
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/LvPlan/getRoomplan',
|
||||
params: { ort_kurzbz, start_date, end_date }
|
||||
};
|
||||
},
|
||||
getStundenplan(start_date, end_date, lv_id) {
|
||||
getLvPlan(start_date, end_date, lv_id) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Stundenplan/getStundenplan',
|
||||
url: '/api/frontend/v1/LvPlan/getLvPlan',
|
||||
params: { start_date, end_date, lv_id }
|
||||
};
|
||||
},
|
||||
eventsPersonal(start_date, end_date) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/lvPlan/eventsPersonal',
|
||||
params: { start_date, end_date }
|
||||
};
|
||||
},
|
||||
eventsLv(lv_id, start_date, end_date) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/lvPlan/eventsLv',
|
||||
params: { lv_id, start_date, end_date }
|
||||
};
|
||||
},
|
||||
getStunden() {
|
||||
// TODO(chris): seems to be called from nowhere?
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Stundenplan/Stunden'
|
||||
url: '/api/frontend/v1/LvPlan/Stunden'
|
||||
};
|
||||
},
|
||||
getOrtReservierungen(ort_kurzbz, start_date, end_date) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: `/api/frontend/v1/Stundenplan/Reservierungen/${ort_kurzbz}`,
|
||||
method: 'post',
|
||||
url: `/api/frontend/v1/LvPlan/getReservierungen/${ort_kurzbz}`,
|
||||
params: { start_date, end_date }
|
||||
};
|
||||
},
|
||||
getStundenplanReservierungen(start_date, end_date) {
|
||||
getLvPlanReservierungen(start_date, end_date) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Stundenplan/Reservierungen',
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/LvPlan/getReservierungen',
|
||||
params: { start_date, end_date }
|
||||
};
|
||||
},
|
||||
getLehreinheitStudiensemester(lehreinheit_id) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: `/api/frontend/v1/Stundenplan/getLehreinheitStudiensemester/${lehreinheit_id}`
|
||||
url: `/api/frontend/v1/LvPlan/getLehreinheitStudiensemester/${lehreinheit_id}`
|
||||
};
|
||||
},
|
||||
studiensemesterDateInterval(date) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: `/api/frontend/v1/Stundenplan/studiensemesterDateInterval/${date}`
|
||||
url: `/api/frontend/v1/LvPlan/studiensemesterDateInterval/${date}`
|
||||
};
|
||||
},
|
||||
LvPlanEvents(start_date, end_date, lv_id) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/LvPlan/LvPlanEvents',
|
||||
params: {
|
||||
start_date: start_date,
|
||||
end_date: end_date,
|
||||
lv_id: lv_id
|
||||
}
|
||||
};
|
||||
},
|
||||
getLv(lehrveranstaltung_id) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/LvPlan/getLv/' + lehrveranstaltung_id
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -16,10 +16,15 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
getView(uid) {
|
||||
|
||||
profilViewData(uid) {
|
||||
let url = "/api/frontend/v1/Profil/profilViewData";
|
||||
if(uid){
|
||||
url += `/${uid}`;
|
||||
}
|
||||
return {
|
||||
method: 'get',
|
||||
url: `/api/frontend/v1/Profil/getView/${uid}`
|
||||
url: url
|
||||
};
|
||||
},
|
||||
fotoSperre(value) {
|
||||
@@ -69,5 +74,5 @@ export default {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Profil/getAllNationen'
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -61,6 +61,13 @@ export default {
|
||||
params: dms
|
||||
};
|
||||
},
|
||||
updateProfilbild(dms) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: `/api/frontend/v1/ProfilUpdate/updateProfilbild`,
|
||||
params: dms
|
||||
};
|
||||
},
|
||||
getProfilRequestFiles(requestID) {
|
||||
return {
|
||||
method: 'get',
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
export default {
|
||||
|
||||
loadRenderers() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/RendererLoader/GetRenderers',
|
||||
params: {
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
@@ -22,5 +22,19 @@ export default {
|
||||
url: '/api/frontend/v1/searchbar/search',
|
||||
params
|
||||
};
|
||||
},
|
||||
searchCis(params) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/searchbar/searchCis',
|
||||
params
|
||||
};
|
||||
},
|
||||
searchStv(params) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/searchbar/searchStv',
|
||||
params
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
export default {
|
||||
getArchiv(person_id) {
|
||||
console.log(person_id);
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/archiv/getArchiv',
|
||||
|
||||
@@ -16,16 +16,22 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
get(prestudent_id) {
|
||||
get(prestudent_id, studiensemester_kurzbz) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/student/get/' + prestudent_id
|
||||
url: 'api/frontend/v1/stv/student/get/'
|
||||
+ encodeURIComponent(prestudent_id)
|
||||
+ '/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
};
|
||||
},
|
||||
save(prestudent_id, params) {
|
||||
save(prestudent_id, studiensemester_kurzbz, params) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/student/save/' + prestudent_id,
|
||||
url: 'api/frontend/v1/stv/student/save/'
|
||||
+ encodeURIComponent(prestudent_id)
|
||||
+ '/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz),
|
||||
params
|
||||
};
|
||||
},
|
||||
|
||||
@@ -22,25 +22,34 @@ export default {
|
||||
url: 'api/frontend/v1/stv/grades/list'
|
||||
};
|
||||
},
|
||||
getCertificate(prestudent_id, all) {
|
||||
all = all ? '/all' : '';
|
||||
getCertificate(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/grades/getCertificate/' + encodeURIComponent(prestudent_id);
|
||||
if (!!studiensemester_kurzbz) {
|
||||
url = url + '/' + encodeURIComponent(studiensemester_kurzbz);
|
||||
}
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/grades/getCertificate/' + prestudent_id + all
|
||||
url: url
|
||||
};
|
||||
},
|
||||
getTeacherProposal(prestudent_id, all) {
|
||||
all = all ? '/all' : '';
|
||||
getTeacherProposal(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/grades/getTeacherProposal/' + encodeURIComponent(prestudent_id);
|
||||
if (!!studiensemester_kurzbz) {
|
||||
url = url + '/' + encodeURIComponent(studiensemester_kurzbz);
|
||||
}
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/grades/getTeacherProposal/' + prestudent_id + all
|
||||
url: url
|
||||
};
|
||||
},
|
||||
getRepeaterGrades(prestudent_id, all) {
|
||||
all = all ? '/all' : '';
|
||||
getRepeaterGrades(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/grades/getRepeaterGrades/' + encodeURIComponent(prestudent_id);
|
||||
if (!!studiensemester_kurzbz) {
|
||||
url = url + '/' + encodeURIComponent(studiensemester_kurzbz);
|
||||
}
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/grades/getRepeaterGrades/' + prestudent_id + all
|
||||
url: url
|
||||
};
|
||||
},
|
||||
updateCertificate({lehrveranstaltung_id, student_uid, studiensemester_kurzbz, note, lehrveranstaltung_bezeichnung}) {
|
||||
@@ -86,11 +95,15 @@ export default {
|
||||
}
|
||||
};
|
||||
},
|
||||
getGradeFromPoints(points, lehrveranstaltung_id) {
|
||||
getGradeFromPoints(points, lehrveranstaltung_id, studiensemester_kurzbz) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/grades/getGradeFromPoints',
|
||||
params: { points, lehrveranstaltung_id }
|
||||
params: {
|
||||
"points": points,
|
||||
"lehrveranstaltung_id": lehrveranstaltung_id,
|
||||
"studiensemester_kurzbz": studiensemester_kurzbz
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -17,10 +17,13 @@
|
||||
|
||||
export default {
|
||||
//------------- Prestudent.js------------------------------------------------------
|
||||
get(prestudent_id) {
|
||||
get(prestudent_id, studiensemester_kurzbz) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/prestudent/get/' + prestudent_id
|
||||
url: 'api/frontend/v1/stv/prestudent/get/'
|
||||
+ encodeURIComponent(prestudent_id)
|
||||
+ '/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
};
|
||||
},
|
||||
updatePrestudent(prestudent_id, params) {
|
||||
|
||||
@@ -16,22 +16,34 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
uid(uid) {
|
||||
uid(uid, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/students/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
+ '/uid/'
|
||||
+ encodeURIComponent(uid);
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/students/uid/' + uid
|
||||
url: url
|
||||
};
|
||||
},
|
||||
prestudent(prestudent_id) {
|
||||
prestudent(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/students/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
+ '/prestudent/'
|
||||
+ encodeURIComponent(prestudent_id);
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/students/prestudent/' + prestudent_id
|
||||
url: url
|
||||
};
|
||||
},
|
||||
person(person_id) {
|
||||
person(person_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/students/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
+ '/person/'
|
||||
+ encodeURIComponent(person_id);
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/students/person/' + person_id
|
||||
url: url
|
||||
};
|
||||
},
|
||||
verband(relative_path) {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (C) 2025 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export default {
|
||||
getVorlagen() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/vorlagen/vorlagen/getVorlagen/'
|
||||
};
|
||||
},
|
||||
getVorlagenByLoggedInUser() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/vorlagen/vorlagen/getVorlagenByLoggedInUser/'
|
||||
};
|
||||
},
|
||||
|
||||
};
|
||||
@@ -22,7 +22,7 @@ import filter from "./filter.js";
|
||||
import studstatus from "./studstatus.js";
|
||||
import profil from "./profil.js";
|
||||
import profilUpdate from "./profilUpdate.js";
|
||||
import stundenplan from "./stundenplan.js";
|
||||
import lvPlan from "./lvPlan.js";
|
||||
import bookmark from "./bookmark.js";
|
||||
import stv from "./stv.js";
|
||||
import notiz from "./notiz.js";
|
||||
@@ -39,6 +39,8 @@ import studiengang from "./studiengang.js";
|
||||
import menu from "./menu.js";
|
||||
import dashboard from "./dashboard.js";
|
||||
import authinfo from "./authinfo.js";
|
||||
import studium from "./studium.js";
|
||||
import language from "./language.js";
|
||||
|
||||
export default {
|
||||
search,
|
||||
@@ -49,7 +51,7 @@ export default {
|
||||
studstatus,
|
||||
profil,
|
||||
profilUpdate,
|
||||
stundenplan,
|
||||
lvPlan,
|
||||
bookmark,
|
||||
stv,
|
||||
notiz,
|
||||
@@ -66,4 +68,6 @@ export default {
|
||||
studiengang,
|
||||
menu,
|
||||
authinfo,
|
||||
studium,
|
||||
language
|
||||
};
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (C) 2024 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export default {
|
||||
getAll() {
|
||||
return this.$fhcApi.get('/api/frontend/v1/language/get');
|
||||
}
|
||||
};
|
||||
@@ -18,5 +18,80 @@ export default {
|
||||
`/api/frontend/v1/Lehre/Pruefungen/${lehrveranstaltung_id}`
|
||||
, {}
|
||||
);
|
||||
},
|
||||
getStudentProjektarbeiten(uid) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Lehre/getStudentProjektarbeiten/${uid}`
|
||||
, {}
|
||||
);
|
||||
},
|
||||
getStudentProjektabgaben(detail) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Lehre/getStudentProjektabgaben`
|
||||
, {
|
||||
projektarbeit_id: detail.projektarbeit_id,
|
||||
student_uid: detail.student_uid
|
||||
}
|
||||
);
|
||||
},
|
||||
postStudentProjektarbeitEndupload(formData) {
|
||||
const url = '/api/frontend/v1/Lehre/postStudentProjektarbeitEndupload';
|
||||
const headers = {Headers: { "Content-Type": "multipart/form-data" }}
|
||||
return this.$fhcApi.post(url, formData, headers)
|
||||
},
|
||||
postStudentProjektarbeitZwischenabgabe(formData) {
|
||||
const url = '/api/frontend/v1/Lehre/postStudentProjektarbeitZwischenabgabe';
|
||||
const headers = {Headers: { "Content-Type": "multipart/form-data" }}
|
||||
return this.$fhcApi.post(url, formData, headers)
|
||||
},
|
||||
getStudentProjektarbeitAbgabeFile(paabgabe_id, student_uid) {
|
||||
const url = `/Cis/Abgabetool/getStudentProjektarbeitAbgabeFile?paabgabe_id=${paabgabe_id}&student_uid=${student_uid}`;
|
||||
|
||||
window.location = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + url
|
||||
},
|
||||
getMitarbeiterProjektarbeiten(uid, all) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Lehre/getMitarbeiterProjektarbeiten?showall=${all}`
|
||||
, {}
|
||||
);
|
||||
},
|
||||
postProjektarbeitAbgabe(termin) {
|
||||
const payload = {
|
||||
paabgabe_id: termin.paabgabe_id,
|
||||
paabgabetyp_kurzbz: termin.bezeichnung.paabgabetyp_kurzbz,
|
||||
datum: termin.datum,
|
||||
fixtermin: termin.fixtermin,
|
||||
insertvon: termin.insertvon,
|
||||
kurzbz: termin.kurzbz,
|
||||
projektarbeit_id: termin.projektarbeit_id
|
||||
}
|
||||
const url = '/api/frontend/v1/Lehre/postProjektarbeitAbgabe';
|
||||
|
||||
return this.$fhcApi.post(url, payload, null)
|
||||
|
||||
},
|
||||
deleteProjektarbeitAbgabe(paabgabe_id) {
|
||||
const payload = {
|
||||
paabgabe_id
|
||||
}
|
||||
const url = '/api/frontend/v1/Lehre/deleteProjektarbeitAbgabe';
|
||||
|
||||
return this.$fhcApi.post(url, payload, null)
|
||||
},
|
||||
postSerientermin(datum, paabgabetyp_kurzbz, bezeichnung, kurzbz, projektarbeit_ids) {
|
||||
const payload = {
|
||||
datum, paabgabetyp_kurzbz, bezeichnung, kurzbz, projektarbeit_ids
|
||||
}
|
||||
const url = '/api/frontend/v1/Lehre/postSerientermin';
|
||||
|
||||
return this.$fhcApi.post(url, payload, null)
|
||||
},
|
||||
fetchDeadlines(person_id) {
|
||||
const payload = {
|
||||
person_id
|
||||
}
|
||||
const url = '/api/frontend/v1/Lehre/fetchDeadlines';
|
||||
|
||||
return this.$fhcApi.post(url, payload, null)
|
||||
}
|
||||
}
|
||||
@@ -7,16 +7,24 @@ export default {
|
||||
url: '/api/frontend/v1/Lehrveranstaltung/loadByLV/' + encodeURIComponent(lehrveranstaltung_id)
|
||||
};
|
||||
},
|
||||
getByStg(studiengang_kz, semester)
|
||||
getByStg(studiensemester_kurzbz, studiengang_kz, semester = null)
|
||||
{
|
||||
return ("/api/frontend/v1/Lehrveranstaltung/loadByStudiengang/" + encodeURIComponent(studiengang_kz) + "/" + encodeURIComponent(semester));
|
||||
},
|
||||
let path = "/api/frontend/v1/Lehrveranstaltung/getByStg/" + encodeURIComponent(studiensemester_kurzbz) + "/" + encodeURIComponent(studiengang_kz);
|
||||
|
||||
getByEmpStg(mitarbeiter_uid, stg)
|
||||
if (semester)
|
||||
path += "/" + encodeURIComponent(semester);
|
||||
|
||||
return path;
|
||||
},
|
||||
getByEmp(studiensemester_kurzbz, mitarbeiter_uid, stg = null)
|
||||
{
|
||||
return ("/api/frontend/v1/Lehrveranstaltung/loadByEmployee/" + encodeURIComponent(mitarbeiter_uid) + "/" + encodeURIComponent(stg));
|
||||
},
|
||||
let path = "/api/frontend/v1/Lehrveranstaltung/getByEmp/" + encodeURIComponent(studiensemester_kurzbz) + "/" + encodeURIComponent(mitarbeiter_uid);
|
||||
|
||||
if (stg)
|
||||
path += "/" + encodeURIComponent(stg);
|
||||
|
||||
return path;
|
||||
},
|
||||
getTable(url)
|
||||
{
|
||||
return {
|
||||
|
||||
@@ -30,7 +30,7 @@ export default {
|
||||
{
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/lv/gruppe/deleteLVPlan/',
|
||||
url: '/api/frontend/v1/lv/gruppe/deleteFromLVPlan/',
|
||||
params: deleteData
|
||||
};
|
||||
},
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
|
||||
export default {
|
||||
getRoomInfo(ort_kurzbz, start_date, end_date) {
|
||||
return this.$fhcApi.post(
|
||||
'/api/frontend/v1/LvPlan/getRoomplan',
|
||||
{ ort_kurzbz, start_date, end_date}
|
||||
);
|
||||
},
|
||||
getStunden() {
|
||||
return this.$fhcApi.get(
|
||||
'/api/frontend/v1/LvPlan/Stunden',
|
||||
{}
|
||||
);
|
||||
},
|
||||
getOrtReservierungen(ort_kurzbz, start_date, end_date) {
|
||||
return this.$fhcApi.post(
|
||||
`/api/frontend/v1/LvPlan/getReservierungen/${ort_kurzbz}`,
|
||||
{ start_date, end_date}
|
||||
);
|
||||
},
|
||||
getLvPlanReservierungen(start_date, end_date) {
|
||||
return this.$fhcApi.post(
|
||||
'/api/frontend/v1/LvPlan/getReservierungen',
|
||||
{ start_date, end_date }
|
||||
);
|
||||
},
|
||||
getLehreinheitStudiensemester(lehreinheit_id) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/LvPlan/getLehreinheitStudiensemester/${lehreinheit_id}`,
|
||||
{}
|
||||
);
|
||||
},
|
||||
studiensemesterDateInterval(date) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/LvPlan/studiensemesterDateInterval/${date}`,
|
||||
{}
|
||||
);
|
||||
},
|
||||
LvPlanEvents(start_date, end_date, lv_id) {
|
||||
return this.$fhcApi.get(
|
||||
'/api/frontend/v1/LvPlan/LvPlanEvents',
|
||||
{ start_date, end_date, lv_id }
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
};
|
||||
@@ -7,4 +7,19 @@ export default {
|
||||
{ ort_kurzbz: ort_kurbz }
|
||||
);
|
||||
},
|
||||
getRooms(datum, von, bis, typ, personenanzahl = 0) {
|
||||
return this.$fhcApi.get(
|
||||
FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
|
||||
"/api/frontend/v1/Ort/getRooms",
|
||||
{ datum, von, bis, typ, personenanzahl }
|
||||
);
|
||||
},
|
||||
getRoomTypes() {
|
||||
return this.$fhcApi.get(
|
||||
FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
|
||||
"/api/frontend/v1/Ort/getTypes"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,16 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
search(searchsettings) {
|
||||
search(searchsettings, config) {
|
||||
const url = '/api/frontend/v1/searchbar/search';
|
||||
return this.$fhcApi.post(url, searchsettings, config);
|
||||
},
|
||||
searchAdvanced(searchsettings, config) {
|
||||
const url = '/api/frontend/v1/searchbar/searchAdvanced';
|
||||
return this.$fhcApi.post(url, searchsettings, config);
|
||||
},
|
||||
searchdummy(searchsettings) {
|
||||
const url = 'public/js/apps/api/dummyapi.php/Search';
|
||||
return this.$fhcApi.post(url, searchsettings);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
export default {
|
||||
|
||||
getStudiensemester: function () {
|
||||
return this.$fhcApi.get(
|
||||
'/components/Cis/Mylv/Studiensemester',
|
||||
{}
|
||||
);
|
||||
},
|
||||
|
||||
getAllStudienSemester: function (studiensemester=undefined, studiengang=undefined, semester=undefined, studienplan=undefined) {
|
||||
return this.$fhcApi.get(
|
||||
'/api/frontend/v1/Studium/getStudienAllSemester',
|
||||
{studiensemester, studiengang, semester, studienplan}
|
||||
);
|
||||
},
|
||||
|
||||
getStudiengaengeForStudienSemester: function (studiensemester) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Studium/getStudiengaengeForStudienSemester/${studiensemester}`,
|
||||
{}
|
||||
);
|
||||
},
|
||||
getStudienplaeneBySemester: function (studiengang, studiensemester) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Studium/getStudienplaeneBySemester`,
|
||||
{
|
||||
studiengang,
|
||||
studiensemester,
|
||||
}
|
||||
);
|
||||
},
|
||||
getLvPlanForStudiensemester: function (studiensemester, lvid) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/LvPlan/getLvPlanForStudiensemester/${studiensemester}/${lvid}`,
|
||||
{
|
||||
}
|
||||
);
|
||||
},
|
||||
getLvEvaluierungInfo: function (studiensemester_kurzbz, lvid) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Studium/getLvEvaluierungInfo/${studiensemester_kurzbz}/${lvid}`,
|
||||
{
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
|
||||
export default {
|
||||
getRoomInfo(ort_kurzbz, start_date, end_date) {
|
||||
return this.$fhcApi.get(
|
||||
'/api/frontend/v1/Stundenplan/getRoomplan',
|
||||
{ ort_kurzbz, start_date, end_date}
|
||||
);
|
||||
},
|
||||
getStundenplan(start_date, end_date, lv_id) {
|
||||
return this.$fhcApi.get(
|
||||
'/api/frontend/v1/Stundenplan/getStundenplan',
|
||||
{ start_date, end_date, lv_id }
|
||||
);
|
||||
},
|
||||
getStunden() {
|
||||
return this.$fhcApi.get(
|
||||
'/api/frontend/v1/Stundenplan/Stunden',
|
||||
{}
|
||||
);
|
||||
},
|
||||
getOrtReservierungen(ort_kurzbz, start_date, end_date) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Stundenplan/Reservierungen/${ort_kurzbz}`,
|
||||
{ start_date, end_date}
|
||||
);
|
||||
},
|
||||
getStundenplanReservierungen(start_date, end_date) {
|
||||
return this.$fhcApi.get(
|
||||
'/api/frontend/v1/Stundenplan/Reservierungen',
|
||||
{ start_date, end_date }
|
||||
);
|
||||
},
|
||||
getLehreinheitStudiensemester(lehreinheit_id) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Stundenplan/getLehreinheitStudiensemester/${lehreinheit_id}`,
|
||||
{}
|
||||
);
|
||||
},
|
||||
studiensemesterDateInterval(date) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Stundenplan/studiensemesterDateInterval/${date}`,
|
||||
{}
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -1,10 +1,16 @@
|
||||
export default {
|
||||
get(prestudent_id) {
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/student/get/' + prestudent_id);
|
||||
get(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/student/get/'
|
||||
+ encodeURIComponent(prestudent_id)
|
||||
+ '/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz);
|
||||
return this.$fhcApi.post(url);
|
||||
},
|
||||
save(form, prestudent_id, data) {
|
||||
return this.$fhcApi.post(form, 'api/frontend/v1/stv/student/save/' + prestudent_id,
|
||||
data
|
||||
);
|
||||
save(form, prestudent_id, studiensemester_kurzbz, data) {
|
||||
let url = 'api/frontend/v1/stv/student/save/'
|
||||
+ encodeURIComponent(prestudent_id)
|
||||
+ '/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz);
|
||||
return this.$fhcApi.post(form, url, data);
|
||||
},
|
||||
}
|
||||
|
||||
+27
-11
@@ -2,17 +2,26 @@ export default {
|
||||
list() {
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/grades/list');
|
||||
},
|
||||
getCertificate(prestudent_id, all) {
|
||||
all = all ? '/all' : '';
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/grades/getCertificate/' + prestudent_id + all);
|
||||
getCertificate(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/grades/getCertificate/' + encodeURIComponent(prestudent_id);
|
||||
if (!!studiensemester_kurzbz) {
|
||||
url = url + '/' + encodeURIComponent(studiensemester_kurzbz);
|
||||
}
|
||||
return this.$fhcApi.get(url);
|
||||
},
|
||||
getTeacherProposal(prestudent_id, all) {
|
||||
all = all ? '/all' : '';
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/grades/getTeacherProposal/' + prestudent_id + all);
|
||||
getTeacherProposal(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/grades/getTeacherProposal/' + encodeURIComponent(prestudent_id);
|
||||
if (!!studiensemester_kurzbz) {
|
||||
url = url + '/' + encodeURIComponent(studiensemester_kurzbz);
|
||||
}
|
||||
return this.$fhcApi.get(url);
|
||||
},
|
||||
getRepeaterGrades(prestudent_id, all) {
|
||||
all = all ? '/all' : '';
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/grades/getRepeaterGrades/' + prestudent_id + all);
|
||||
getRepeaterGrades(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/grades/getRepeaterGrades/' + encodeURIComponent(prestudent_id);
|
||||
if (!!studiensemester_kurzbz) {
|
||||
url = url + '/' + encodeURIComponent(studiensemester_kurzbz);
|
||||
}
|
||||
return this.$fhcApi.get(url);
|
||||
},
|
||||
updateCertificate({lehrveranstaltung_id, student_uid, studiensemester_kurzbz, note, lehrveranstaltung_bezeichnung}) {
|
||||
return this.$fhcApi.post(
|
||||
@@ -65,8 +74,15 @@ export default {
|
||||
}
|
||||
);
|
||||
},
|
||||
getGradeFromPoints(points, lehrveranstaltung_id, manualErrorHandling) {
|
||||
getGradeFromPoints(points, lehrveranstaltung_id, studiensemester_kurzbz, manualErrorHandling) {
|
||||
const config = manualErrorHandling ? {errorHandling: false} : {};
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/grades/getGradeFromPoints', {points, lehrveranstaltung_id}, config);
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/grades/getGradeFromPoints',
|
||||
{
|
||||
"points": points,
|
||||
"lehrveranstaltung_id": lehrveranstaltung_id,
|
||||
"studiensemester_kurzbz": studiensemester_kurzbz
|
||||
},
|
||||
config
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,13 @@ export default {
|
||||
|
||||
//------------- Prestudent.js------------------------------------------------------
|
||||
|
||||
get(prestudent_id){
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/prestudent/get/' + prestudent_id);
|
||||
get(prestudent_id, studiensemester_kurzbz) {
|
||||
return this.$fhcApi.post(
|
||||
'api/frontend/v1/stv/prestudent/get/'
|
||||
+ encodeURIComponent(prestudent_id)
|
||||
+ '/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
);
|
||||
},
|
||||
updatePrestudent(form, prestudent_id, data){
|
||||
return this.$fhcApi.post(form, 'api/frontend/v1/stv/prestudent/updatePrestudent/' + prestudent_id,
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
export default {
|
||||
uid(uid) {
|
||||
return this.$fhcApi.getUri('api/frontend/v1/stv/students/uid/' + uid);
|
||||
uid(uid, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/students/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
+ '/uid/'
|
||||
+ encodeURIComponent(uid);
|
||||
return this.$fhcApi.getUri(url);
|
||||
},
|
||||
prestudent(prestudent_id) {
|
||||
return this.$fhcApi.getUri('api/frontend/v1/stv/students/prestudent/' + prestudent_id);
|
||||
prestudent(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/students/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
+ '/prestudent/'
|
||||
+ encodeURIComponent(prestudent_id);
|
||||
return this.$fhcApi.getUri(url);
|
||||
},
|
||||
person(person_id) {
|
||||
return this.$fhcApi.getUri('api/frontend/v1/stv/students/person/' + person_id);
|
||||
person(person_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/students/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
+ '/person/'
|
||||
+ encodeURIComponent(person_id);
|
||||
return this.$fhcApi.getUri(url);
|
||||
},
|
||||
verband(relative_path) {
|
||||
return this.$fhcApi.getUri('api/frontend/v1/stv/students/' + relative_path);
|
||||
|
||||
+37
-9
@@ -2,6 +2,7 @@ import FhcSearchbar from "../components/searchbar/searchbar.js";
|
||||
import CisMenu from "../components/Cis/Menu.js";
|
||||
import PluginsPhrasen from '../plugins/Phrasen.js';
|
||||
import ApiSearchbar from '../api/factory/searchbar.js';
|
||||
import Theme from "../plugins/Theme.js";
|
||||
|
||||
const app = Vue.createApp({
|
||||
name: 'CisApp',
|
||||
@@ -12,14 +13,17 @@ const app = Vue.createApp({
|
||||
data: function() {
|
||||
return {
|
||||
searchbaroptions: {
|
||||
origin: "cis",
|
||||
cssclass: "",
|
||||
calcheightonly: true,
|
||||
types: [
|
||||
"mitarbeiter",
|
||||
"student",
|
||||
"raum",
|
||||
"organisationunit"
|
||||
],
|
||||
types: {
|
||||
employee: Vue.computed(() => this.$p.t("search/type_employee")),
|
||||
student: Vue.computed(() => this.$p.t("search/type_student")),
|
||||
room: Vue.computed(() => this.$p.t("search/type_room")),
|
||||
organisationunit: Vue.computed(() => this.$p.t("search/type_organisationunit")),
|
||||
cms: Vue.computed(() => this.$p.t("search/type_cms")),
|
||||
dms: Vue.computed(() => this.$p.t("search/type_dms"))
|
||||
},
|
||||
actions: {
|
||||
employee: {
|
||||
defaultaction: {
|
||||
@@ -42,7 +46,7 @@ const app = Vue.createApp({
|
||||
},
|
||||
childactions: []
|
||||
},
|
||||
raum: {
|
||||
room: {
|
||||
defaultaction: {
|
||||
type: "link",
|
||||
renderif: function(data) {
|
||||
@@ -104,14 +108,37 @@ const app = Vue.createApp({
|
||||
}
|
||||
},
|
||||
childactions: []
|
||||
}
|
||||
},
|
||||
cms: {
|
||||
defaultaction: {
|
||||
type: "link",
|
||||
action: function (data) {
|
||||
const link = FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
|
||||
'/CisVue/Cms/content/' + data.content_id;
|
||||
return link;
|
||||
}
|
||||
},
|
||||
childactions: []
|
||||
},
|
||||
dms: {
|
||||
defaultaction: {
|
||||
type: "link",
|
||||
action: function (data) {
|
||||
const link = FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
'cms/dms.php?id=' + data.dms_id;
|
||||
return link;
|
||||
}
|
||||
},
|
||||
childactions: []
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
searchfunction: function(searchsettings) {
|
||||
return this.$api.call(ApiSearchbar.search(searchsettings));
|
||||
return this.$api.call(ApiSearchbar.searchCis(searchsettings));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -122,4 +149,5 @@ app.use(primevue.config.default, {
|
||||
}
|
||||
})
|
||||
app.use(PluginsPhrasen);
|
||||
app.use(Theme);
|
||||
app.mount('#cis-header');
|
||||
|
||||
+114
-50
@@ -1,20 +1,35 @@
|
||||
import FhcDashboard from '../../components/Dashboard/Dashboard.js';
|
||||
import PluginsPhrasen from '../../plugins/Phrasen.js';
|
||||
import Theme from '../../plugins/Theme.js';
|
||||
import contrast from '../../directives/contrast.js';
|
||||
import {setScrollbarWidth} from "../../helpers/CssVarCalcHelpers.js";
|
||||
import Stundenplan, {DEFAULT_MODE_STUNDENPLAN} from "../../components/Cis/Stundenplan/Stundenplan.js";
|
||||
import LvPlan from "../../components/Cis/LvPlan/Lehrveranstaltung.js";
|
||||
import MyLvPlan from "../../components/Cis/LvPlan/Personal.js";
|
||||
import MylvStudent from "../../components/Cis/Mylv/Student.js";
|
||||
import Profil from "../../components/Cis/Profil/Profil.js";
|
||||
import Raumsuche from "../../components/Cis/Raumsuche/Raumsuche.js";
|
||||
import CmsNews from "../../components/Cis/Cms/News.js";
|
||||
import CmsContent from "../../components/Cis/Cms/Content.js";
|
||||
import Info from "../../components/Cis/Mylv/Semester/Studiengang/Lv/Info.js";
|
||||
import RoomInformation, {DEFAULT_MODE_RAUMINFO} from "../../components/Cis/Mylv/RoomInformation.js";
|
||||
import AbgabetoolStudent from "../../components/Cis/Abgabetool/AbgabetoolStudent.js";
|
||||
import AbgabetoolMitarbeiter from "../../components/Cis/Abgabetool/AbgabetoolMitarbeiter.js";
|
||||
import DeadlineOverview from "../../components/Cis/Abgabetool/DeadlineOverview.js";
|
||||
import Studium from "../../components/Cis/Studium/Studium.js";
|
||||
|
||||
import ApiRenderers from '../../api/factory/renderers.js';
|
||||
|
||||
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
|
||||
|
||||
const router = VueRouter.createRouter({
|
||||
history: VueRouter.createWebHistory(`/${ciPath}`),
|
||||
routes: [
|
||||
{
|
||||
path: `/Cis/Studium`,
|
||||
name: 'Studium',
|
||||
component: Studium,
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: `/Cis/Profil/View/:uid`,
|
||||
name: 'ProfilView',
|
||||
@@ -27,7 +42,30 @@ const router = VueRouter.createRouter({
|
||||
component: Profil,
|
||||
props: true
|
||||
},
|
||||
|
||||
{
|
||||
path: `/Cis/Abgabetool/Student/:student_uid_prop?`,
|
||||
name: 'AbgabetoolStudent',
|
||||
component: AbgabetoolStudent,
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: `/Cis/Abgabetool/Mitarbeiter`,
|
||||
name: 'AbgabetoolMitarbeiter',
|
||||
component: AbgabetoolMitarbeiter,
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: `/Cis/Abgabetool/Deadlines/:person_uid_prop?`,
|
||||
name: 'DeadlineOverview',
|
||||
component: DeadlineOverview,
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: `/Cis/Raumsuche`,
|
||||
name: 'Raumsuche',
|
||||
component: Raumsuche,
|
||||
props: true
|
||||
},
|
||||
// Redirect old links to new format
|
||||
{
|
||||
path: "/CisVue/Cms/getRoomInformation/:ort_kurzbz",
|
||||
@@ -113,59 +151,41 @@ const router = VueRouter.createRouter({
|
||||
},
|
||||
// Redirect old links to new format
|
||||
{
|
||||
// only trigger on first param being numeric to avoid paths like "Stundenplan/Month" entering here
|
||||
path: "/Cis/Stundenplan/:lv_id(\\d+)",
|
||||
name: "StundenplanOld",
|
||||
component: Stundenplan,
|
||||
redirect: (to) => {
|
||||
return { // redirect to longer Stundenplan url and map params
|
||||
name: "Stundenplan",
|
||||
// only trigger on first param being numeric to avoid paths like "LvPlan/Month" entering here
|
||||
path: "/Cis/LvPlan/:lv_id(\\d+)",
|
||||
name: "LvPlanOld",
|
||||
component: LvPlan,
|
||||
redirect(to) {
|
||||
const route = Vue.unref(router.currentRoute);
|
||||
const { mode, focus_date } = route.params; // keep mode and focus_date if available
|
||||
return { // redirect to longer LvPlan url and map params
|
||||
name: "LvPlan",
|
||||
params: {
|
||||
mode,
|
||||
focus_date,
|
||||
lv_id: to.params.lv_id
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
path: `/Cis/Stundenplan/:mode?/:focus_date?/:lv_id?`,
|
||||
name: 'Stundenplan',
|
||||
component: Stundenplan,
|
||||
props: (route) => { // validate and set mode/focus date if for some reason missing
|
||||
const validModes = ["Month", "Week", "Day"];
|
||||
|
||||
// check mode string
|
||||
const mode = route.params.mode &&
|
||||
validModes.includes(route.params.mode.charAt(0).toUpperCase() + route.params.mode.slice(1).toLowerCase())
|
||||
? route.params.mode.charAt(0).toUpperCase() + route.params.mode.slice(1).toLowerCase()
|
||||
: DEFAULT_MODE_STUNDENPLAN;
|
||||
|
||||
// default to today date if not provided or string forms invalid date
|
||||
const d = new Date(route.params.focus_date)
|
||||
const focus_date = !isNaN(d) ? route.params.focus_date : new Date().toISOString().split("T")[0];
|
||||
// for consistency reasons format the props into one object but actually use a new name to we dont collide with
|
||||
// existing viewData declaration written from codeigniter 3 into routerview tag
|
||||
path: `/Cis/LvPlan/:mode?/:focus_date?/:lv_id?`,
|
||||
name: 'LvPlan',
|
||||
component: LvPlan,
|
||||
props(route) {
|
||||
return {
|
||||
propsViewData: {
|
||||
mode,
|
||||
focus_date,
|
||||
lv_id: route.params.lv_id
|
||||
}
|
||||
propsViewData: route.params
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: `/Cis/MyLvPlan/:mode?/:focus_date?`,
|
||||
name: 'MyLvPlan',
|
||||
component: MyLvPlan,
|
||||
props(route) {
|
||||
return {
|
||||
propsViewData: route.params
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
// missing mode or focus_date -> set defaults
|
||||
if (!to.params.mode || !to.params.focus_date) {
|
||||
next({
|
||||
name: "Stundenplan",
|
||||
params: {
|
||||
mode: to.params.mode || DEFAULT_MODE_STUNDENPLAN,
|
||||
focus_date: to.params.focus_date || new Date().toISOString().split("T")[0],
|
||||
lv_id: to.params.lv_id
|
||||
}
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -200,7 +220,8 @@ const router = VueRouter.createRouter({
|
||||
const app = Vue.createApp({
|
||||
name: 'FhcApp',
|
||||
data: () => ({
|
||||
appSideMenuEntries: {}
|
||||
appSideMenuEntries: {},
|
||||
renderers: null,
|
||||
}),
|
||||
components: {},
|
||||
computed: {
|
||||
@@ -210,7 +231,8 @@ const app = Vue.createApp({
|
||||
},
|
||||
provide() {
|
||||
return { // provide injectable & watchable language property
|
||||
language: Vue.computed(() => this.$p.user_language)
|
||||
language: Vue.computed(() => this.$p.user_language),
|
||||
renderers: Vue.computed(() => this.renderers),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -220,7 +242,8 @@ const app = Vue.createApp({
|
||||
},
|
||||
handleClick(event) {
|
||||
const target = event.target.closest('a');
|
||||
|
||||
|
||||
if(target?.id == 'skiplink') return
|
||||
if (target && this.isInternalRoute(target.href)) {
|
||||
const url = new URL(target.href)
|
||||
|
||||
@@ -247,6 +270,45 @@ const app = Vue.createApp({
|
||||
}
|
||||
}
|
||||
},
|
||||
async created(){
|
||||
await this.$api
|
||||
.call(ApiRenderers.loadRenderers())
|
||||
.then(res => res.data)
|
||||
.then(data => {
|
||||
for (let rendertype of Object.keys(data)) {
|
||||
let modalTitle = null;
|
||||
let modalContent = null;
|
||||
let calendarEvent = null;
|
||||
if (data[rendertype].modalTitle)
|
||||
modalTitle = Vue.markRaw(Vue.defineAsyncComponent(() => import(data[rendertype].modalTitle)));
|
||||
if (data[rendertype].modalContent)
|
||||
modalContent = Vue.markRaw(Vue.defineAsyncComponent(() => import(data[rendertype].modalContent)));
|
||||
if (data[rendertype].calendarEvent)
|
||||
calendarEvent = Vue.markRaw(Vue.defineAsyncComponent(() => import(data[rendertype].calendarEvent)));
|
||||
|
||||
if (data[rendertype].calendarEventStyles){
|
||||
var head = document.head;
|
||||
if(!head.querySelector(`link[href="${data[rendertype].calendarEventStyles}"]`)){
|
||||
var link = document.createElement("link");
|
||||
link.type = "text/css";
|
||||
link.rel = "stylesheet";
|
||||
link.href = data[rendertype].calendarEventStyles;
|
||||
head.appendChild(link);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.renderers === null) {
|
||||
this.renderers = {};
|
||||
}
|
||||
if (!this.renderers[rendertype]) {
|
||||
this.renderers[rendertype] = {}
|
||||
}
|
||||
this.renderers[rendertype].modalTitle = modalTitle;
|
||||
this.renderers[rendertype].modalContent = modalContent;
|
||||
this.renderers[rendertype].calendarEvent = calendarEvent;
|
||||
}
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('click', this.handleClick);
|
||||
},
|
||||
@@ -264,6 +326,8 @@ app.use(primevue.config.default, {
|
||||
tooltip: 8000
|
||||
}
|
||||
})
|
||||
app.directive('tooltip', primevue.tooltip);
|
||||
app.use(PluginsPhrasen);
|
||||
app.use(Theme);
|
||||
app.directive('contrast', contrast);
|
||||
app.mount('#fhccontent');
|
||||
app.mount('#fhccontent');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {CoreNavigationCmpt} from '../components/navigation/Navigation.js';
|
||||
import DashboardAdmin from '../components/Dashboard/Admin.js';
|
||||
import Phrases from "../plugin/Phrasen.js"
|
||||
|
||||
Vue.createApp({
|
||||
name: 'DashboardAdminApp',
|
||||
@@ -12,4 +13,4 @@ Vue.createApp({
|
||||
},
|
||||
mounted() {
|
||||
}
|
||||
}).mount('#main');
|
||||
}).use(Phrases).mount('#main');
|
||||
@@ -13,8 +13,36 @@ const router = VueRouter.createRouter({
|
||||
},
|
||||
{
|
||||
name: `byEmp`,
|
||||
path: `/emp/:emp/:stg?`,
|
||||
component: LVVerwaltung
|
||||
path: `/emp/:studiensemester_kurzbz/:emp/:stg?/:semester?`,
|
||||
component: LVVerwaltung,
|
||||
props: route => {
|
||||
let {emp, stg, semester, studiensemester_kurzbz} = route.params;
|
||||
|
||||
if (emp === '')
|
||||
emp = undefined;
|
||||
|
||||
if (stg === '')
|
||||
stg = undefined;
|
||||
|
||||
if (studiensemester_kurzbz === '')
|
||||
studiensemester_kurzbz = undefined;
|
||||
|
||||
return {
|
||||
studiensemester_kurzbz: studiensemester_kurzbz,
|
||||
emp: emp,
|
||||
stg: stg,
|
||||
semester: semester
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const { studiensemester_kurzbz } = to.params;
|
||||
const isSemester = /^(SS|WS)\d{4}$/.test(studiensemester_kurzbz);
|
||||
|
||||
if (!isSemester)
|
||||
return next({ path: '/' });
|
||||
else
|
||||
next();
|
||||
}
|
||||
},
|
||||
/*{
|
||||
name: `byFachbereich`,
|
||||
@@ -23,13 +51,41 @@ const router = VueRouter.createRouter({
|
||||
},*/
|
||||
{
|
||||
name: `byStg`,
|
||||
path: `/stg/:stg/:semester?`,
|
||||
component: LVVerwaltung
|
||||
path: '/stg/:studiensemester_kurzbz/:stg?/:semester?/',
|
||||
component: LVVerwaltung,
|
||||
props: route => {
|
||||
let { studiensemester_kurzbz, stg, semester } = route.params;
|
||||
|
||||
if (semester === '')
|
||||
semester = undefined;
|
||||
|
||||
if (studiensemester_kurzbz === '')
|
||||
studiensemester_kurzbz = undefined;
|
||||
|
||||
if (stg === '')
|
||||
stg = undefined;
|
||||
|
||||
return {
|
||||
studiensemester_kurzbz: studiensemester_kurzbz,
|
||||
stg: stg,
|
||||
semester: semester,
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const studiensemester_kurzbz = to.params?.studiensemester_kurzbz
|
||||
const isSemester = /^(SS|WS)\d{4}$/.test(studiensemester_kurzbz);
|
||||
|
||||
if (!isSemester)
|
||||
return next({ path: '/' });
|
||||
else
|
||||
next();
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
redirect: '/'
|
||||
}
|
||||
},
|
||||
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
@@ -26,14 +26,148 @@ const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*
|
||||
const router = VueRouter.createRouter({
|
||||
history: VueRouter.createWebHistory(),
|
||||
routes: [
|
||||
{ path: `/${ciPath}/studentenverwaltung`, component: FhcStudentenverwaltung },
|
||||
{ path: `/${ciPath}/studentenverwaltung/prestudent/:prestudent_id`, component: FhcStudentenverwaltung },
|
||||
{ path: `/${ciPath}/studentenverwaltung/prestudent/:prestudent_id/:tab`, component: FhcStudentenverwaltung },
|
||||
{ path: `/${ciPath}/studentenverwaltung/student/:id`, component: FhcStudentenverwaltung },
|
||||
{ path: `/${ciPath}/studentenverwaltung/person/:person_id`, component: FhcStudentenverwaltung }
|
||||
{
|
||||
name: 'index',
|
||||
path: `/${ciPath}/studentenverwaltung`,
|
||||
component: FhcStudentenverwaltung
|
||||
},
|
||||
{
|
||||
name: 'studiensemester',
|
||||
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz`,
|
||||
component: FhcStudentenverwaltung,
|
||||
props: (route) => {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
}
|
||||
next();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'studiengang',
|
||||
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/:studiengang`,
|
||||
component: FhcStudentenverwaltung,
|
||||
props: (route) => {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz,
|
||||
url_studiengang: route.params.studiengang,
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
}
|
||||
const isStudiengang = /^[A-Z]{3}/.test(to.params.studiengang);
|
||||
if (!isStudiengang) {
|
||||
return next({
|
||||
name: 'studiensemester',
|
||||
params: {
|
||||
studiensemester_kurzbz: to.params.studiensemester_kurzbz
|
||||
}
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
},
|
||||
{
|
||||
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/prestudent/:prestudent_id`,
|
||||
component: FhcStudentenverwaltung,
|
||||
props: (route) => {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz,
|
||||
url_mode: 'prestudent',
|
||||
url_prestudent_id: route.params.prestudent_id
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
}
|
||||
next();
|
||||
}
|
||||
},
|
||||
{
|
||||
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/prestudent/:prestudent_id/:tab`,
|
||||
component: FhcStudentenverwaltung,
|
||||
props: (route) => {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz,
|
||||
url_mode: 'prestudent',
|
||||
url_prestudent_id: route.params.prestudent_id,
|
||||
url_tab: route.params.tab
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
}
|
||||
next();
|
||||
}
|
||||
},
|
||||
{
|
||||
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/student/:id`,
|
||||
component: FhcStudentenverwaltung,
|
||||
props: (route) => {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz,
|
||||
url_mode: 'student',
|
||||
url_student_id: route.params.id
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
}
|
||||
next();
|
||||
}
|
||||
},
|
||||
{
|
||||
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/person/:person_id`,
|
||||
component: FhcStudentenverwaltung,
|
||||
props: (route) => {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz,
|
||||
url_mode: 'person',
|
||||
url_prestudent_id: route.params.person_id
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
}
|
||||
next();
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
redirect: {
|
||||
name: 'index'
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
router.afterEach((to, from, failure) => {
|
||||
let title = 'Studierendenverwaltung FH-Complete';
|
||||
if (to.params.studiengang) {
|
||||
title = to.params.studiengang + ' ' + title;
|
||||
}
|
||||
if (to.params.studiensemester_kurzbz) {
|
||||
title = to.params.studiensemester_kurzbz + ' ' + title;
|
||||
}
|
||||
document.title = title;
|
||||
});
|
||||
|
||||
const app = Vue.createApp({
|
||||
name: 'StudentenverwaltungApp'
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ export default {
|
||||
:class="this.cModalClass"
|
||||
:id="this.id"
|
||||
:aria-labelledby="this.id + '_label'" aria-hidden="true" tabindex="-1">
|
||||
<div class="modal-dialog" :class="this.cModalDialogClass">
|
||||
<div class="modal-dialog modal-dialog-scrollable" :class="this.cModalDialogClass">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" :id="this.id + '_label'">
|
||||
|
||||
@@ -137,8 +137,6 @@ export default {
|
||||
layout: 'fitColumns',
|
||||
layoutColumnsOnNewData: false,
|
||||
height: '550',
|
||||
selectableRangeMode: 'click',
|
||||
selectable: true,
|
||||
persistenceID: 'core-betriebsmittel'
|
||||
},
|
||||
tabulatorEvents: [
|
||||
@@ -292,12 +290,6 @@ export default {
|
||||
this.formData.anmerkung = null;
|
||||
this.formData.beschreibung = null;
|
||||
this.statusNew = true;
|
||||
},
|
||||
//helper function: workaround to trigger validation if input is not a number
|
||||
normalizeKaution() {
|
||||
if (this.formData.kaution === null || this.formData.kaution === '') {
|
||||
this.formData.kaution = 'xxx'
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -317,6 +309,7 @@ export default {
|
||||
table-only
|
||||
:side-menu="false"
|
||||
reload
|
||||
:reload-btn-infotext="this.$p.t('table', 'reload')"
|
||||
new-btn-show
|
||||
:new-btn-label="this.$p.t('ui', 'betriebsmittel')"
|
||||
@click:new="actionNewBetriebsmittel"
|
||||
@@ -407,10 +400,9 @@ export default {
|
||||
|
||||
<div class="row mb-3">
|
||||
<form-input
|
||||
type="number"
|
||||
type="text"
|
||||
:label="$p.t('infocenter/kaution')"
|
||||
name="kaution"
|
||||
@blur="normalizeKaution"
|
||||
v-model="formData.kaution"
|
||||
>
|
||||
</form-input>
|
||||
@@ -434,6 +426,7 @@ export default {
|
||||
v-model="formData.ausgegebenam"
|
||||
auto-apply
|
||||
:enable-time-picker="false"
|
||||
text-input
|
||||
format="dd.MM.yyyy"
|
||||
preview-format="dd.MM.yyyy"
|
||||
:teleport="true"
|
||||
@@ -449,6 +442,7 @@ export default {
|
||||
v-model="formData.retouram"
|
||||
auto-apply
|
||||
:enable-time-picker="false"
|
||||
text-input
|
||||
format="dd.MM.yyyy"
|
||||
preview-format="dd.MM.yyyy"
|
||||
:teleport="true"
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import CalendarHeader from './Header.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CalendarHeader
|
||||
},
|
||||
inject: [
|
||||
'date',
|
||||
'focusDate',
|
||||
'size'
|
||||
],
|
||||
emits: [
|
||||
'updateMode',
|
||||
'change:range',
|
||||
'input'
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
import BaseDraganddrop from './Base/DragAndDrop.js';
|
||||
import BaseHeader from './Base/Header.js';
|
||||
import BaseSlider from './Base/Slider.js';
|
||||
import BsModal from '../Bootstrap/Modal.js';
|
||||
|
||||
import CalClick from '../../directives/Calendar/Click.js';
|
||||
|
||||
export default {
|
||||
name: "CalendarBase",
|
||||
components: {
|
||||
BaseDraganddrop,
|
||||
BaseHeader,
|
||||
BaseSlider,
|
||||
BsModal
|
||||
},
|
||||
directives: {
|
||||
CalClick
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
locale: Vue.computed(() => this.locale),
|
||||
timezone: Vue.computed(() => this.timezone),
|
||||
timeGrid: Vue.computed(() => this.timeGrid),
|
||||
draggableEvents: Vue.computed(() => {
|
||||
if (!this.draggableEvents)
|
||||
return () => false;
|
||||
|
||||
if (Array.isArray(this.draggableEvents))
|
||||
return event => this.draggableEvents.includes(event.type);
|
||||
if (this.draggableEvents instanceof Function)
|
||||
return this.draggableEvents;
|
||||
|
||||
return () => true;
|
||||
}),
|
||||
dropableEvents: Vue.computed(() => {
|
||||
if (!this.onDrop)
|
||||
return () => false;
|
||||
|
||||
if (Array.isArray(this.dropableEvents))
|
||||
return item => this.dropableEvents.includes(item.type);
|
||||
if (this.dropableEvents instanceof Function)
|
||||
return this.dropableEvents;
|
||||
|
||||
return () => true;
|
||||
}),
|
||||
hasDragoverFunc: Vue.computed(() => this.onDragover),
|
||||
mode: Vue.computed(() => this.mode)
|
||||
};
|
||||
},
|
||||
props: {
|
||||
locale: {
|
||||
type: String,
|
||||
default: 'de'
|
||||
},
|
||||
timezone: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
date: {
|
||||
type: [Date, String, Number, luxon.DateTime],
|
||||
default: props => luxon.DateTime.now().setZone(props.timezone).startOf('day')
|
||||
},
|
||||
modes: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
// TODO(chris): verfication functions
|
||||
},
|
||||
mode: String,
|
||||
modeOptions: Object,
|
||||
events: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
backgrounds: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
showBtns: Boolean,
|
||||
btnMonth: {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
},
|
||||
btnWeek: {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
},
|
||||
btnDay: {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
},
|
||||
btnList: {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
},
|
||||
timeGrid: Array,
|
||||
draggableEvents: [Boolean, Array, Function],
|
||||
dropableEvents: [Boolean, Array, Function],
|
||||
onDragover: Function,
|
||||
onDrop: Function
|
||||
},
|
||||
emits: [
|
||||
"click:next",
|
||||
"click:prev",
|
||||
"click:mode",
|
||||
"click:event",
|
||||
"click:day",
|
||||
"click:week",
|
||||
"update:date",
|
||||
"update:mode",
|
||||
"update:range",
|
||||
"drop"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
internalView: null,
|
||||
internalDate: null,
|
||||
modalEvent: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
convertedEvents() {
|
||||
return this.events.map(orig => ({
|
||||
id: orig.type + orig[orig.type + '_id'],
|
||||
type: orig.type,
|
||||
start: luxon.DateTime.fromISO(orig.isostart).setZone(this.timezone),
|
||||
end: luxon.DateTime.fromISO(orig.isoend).setZone(this.timezone),
|
||||
orig
|
||||
}));
|
||||
},
|
||||
convertedBackgrounds() {
|
||||
return this.backgrounds.map(bg => {
|
||||
const res = { ...bg };
|
||||
if (res.start) {
|
||||
if (Number.isInteger(res.start))
|
||||
res.start = luxon.DateTime.fromMillis(res.start, { zone: this.timezone, locale: this.locale });
|
||||
else if (res.start instanceof Date)
|
||||
res.start = luxon.DateTime.fromJSDate(res.start, { zone: this.timezone, locale: this.locale });
|
||||
else if (typeof res.start ===
|
||||
'string' || res.start instanceof String)
|
||||
res.start = luxon.DateTime.fromISO(res.start, { zone: this.timezone, locale: this.locale });
|
||||
}
|
||||
if (res.end) {
|
||||
if (Number.isInteger(res.end))
|
||||
res.end = luxon.DateTime.fromMillis(res.end, { zone: this.timezone, locale: this.locale });
|
||||
else if (res.end instanceof Date)
|
||||
res.end = luxon.DateTime.fromJSDate(res.end, { zone: this.timezone, locale: this.locale });
|
||||
else if (typeof res.end ===
|
||||
'string' || res.end instanceof String)
|
||||
res.end = luxon.DateTime.fromISO(res.end, { zone: this.timezone, locale: this.locale });
|
||||
}
|
||||
return res;
|
||||
});
|
||||
},
|
||||
sDate() {
|
||||
if (this.date instanceof luxon.DateTime)
|
||||
return this.date;
|
||||
return luxon.DateTime.fromJSDate(new Date(this.date)).setZone(this.timezone);
|
||||
},
|
||||
cDate: {
|
||||
get() {
|
||||
const date = this.internalDate ? this.internalDate : this.sDate;
|
||||
return date.setLocale(this.locale);
|
||||
},
|
||||
set(value) {
|
||||
this.internalDate = value;
|
||||
this.$emit('update:date', value, this.cMode);
|
||||
}
|
||||
},
|
||||
sMode() {
|
||||
// choose default mode
|
||||
let mode = this.mode;
|
||||
if (mode)
|
||||
mode = mode.toLowerCase();
|
||||
if (!mode || !this.modes[mode])
|
||||
mode = Object.keys(this.modes).find(Boolean); // start with first entry as active mode
|
||||
return mode || '';
|
||||
},
|
||||
cMode: {
|
||||
get() {
|
||||
return this.internalView ? this.internalView : this.sMode;
|
||||
},
|
||||
set(value) {
|
||||
this.internalView = value;
|
||||
this.$emit('update:mode', value, this.cDate);
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
sDate(n, o) {
|
||||
if (this.sDate.isValid && !this.sDate.hasSame(this.internalDate, 'day'))
|
||||
this.internalDate = this.sDate;
|
||||
},
|
||||
sMode() {
|
||||
if (this.sMode)
|
||||
this.internalView = this.sMode;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickPrev() {
|
||||
const evt = new Event('click:prev', {cancelable: true});
|
||||
this.$emit('click:prev', evt);
|
||||
if (evt.defaultPrevented)
|
||||
return;
|
||||
|
||||
// default: switch page
|
||||
this.$refs.mode.prevPage();
|
||||
},
|
||||
clickNext() {
|
||||
const evt = new Event('click:next', {cancelable: true});
|
||||
this.$emit('click:next', evt);
|
||||
if (evt.defaultPrevented)
|
||||
return;
|
||||
|
||||
// default: switch page
|
||||
this.$refs.mode.nextPage();
|
||||
},
|
||||
handleClickDefaults(evt) {
|
||||
// TODO(chris): implement
|
||||
switch (evt.detail.source) {
|
||||
case 'day':
|
||||
if (this.cMode != 'day' && this.modes['day']) {
|
||||
evt.stopPropagation();
|
||||
this.cDate = evt.detail.value;
|
||||
this.cMode = 'day';
|
||||
}
|
||||
break;
|
||||
case 'week':
|
||||
if (this.cMode != 'week' && this.modes['week']) {
|
||||
evt.stopPropagation();
|
||||
this.cDate = luxon.DateTime.fromObject({
|
||||
localWeekNumber: evt.detail.value.number,
|
||||
localWeekYear: evt.detail.value.year
|
||||
}, {
|
||||
zone: this.cDate.zoneName,
|
||||
locale: this.cDate.locale
|
||||
});
|
||||
this.cMode = 'week';
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
onDropItem(evt, start, end) {
|
||||
this.$emit('drop', evt, start, end);
|
||||
},
|
||||
showEventModal(eventObj) {
|
||||
this.modalEvent = eventObj;
|
||||
this.$refs.modal.show();
|
||||
},
|
||||
hideEventModal() {
|
||||
if (this.modalEvent)
|
||||
this.modalEvent.closeFn = undefined;
|
||||
this.$refs.modal.hide();
|
||||
this.modalEvent = null;
|
||||
},
|
||||
onModalHidden() {
|
||||
if (this.modalEvent.closeFn)
|
||||
this.modalEvent.closeFn();
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.hideEventModal();
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="fhc-calendar-base h-100">
|
||||
<base-draganddrop
|
||||
class="card h-100"
|
||||
:events="convertedEvents"
|
||||
:backgrounds="convertedBackgrounds"
|
||||
@drop="onDropItem"
|
||||
v-cal-click:container
|
||||
@cal-click-default.capture="handleClickDefaults"
|
||||
>
|
||||
<base-header
|
||||
class="card-header"
|
||||
v-model:date="cDate"
|
||||
v-model:mode="cMode"
|
||||
@prev="clickPrev"
|
||||
@next="clickNext"
|
||||
@click:mode="$emit('click:mode', $event)"
|
||||
:btn-day="!!modes['day'] && (btnDay || (showBtns && btnDay !== false))"
|
||||
:btn-week="!!modes['week'] && (btnWeek || (showBtns && btnWeek !== false))"
|
||||
:btn-month="!!modes['month'] && (btnMonth || (showBtns && btnMonth !== false))"
|
||||
:btn-list="!!modes['list'] && (btnList || (showBtns && btnList !== false))"
|
||||
:mode-options="modeOptions ? modeOptions[cMode] : undefined"
|
||||
>
|
||||
<slot name="actions" />
|
||||
</base-header>
|
||||
<component
|
||||
:is="modes ? modes[cMode] : null || 'div'"
|
||||
ref="mode"
|
||||
v-model:current-date="cDate"
|
||||
@update:range="$emit('update:range', $event)"
|
||||
@request-modal-open="showEventModal"
|
||||
@request-modal-close="hideEventModal"
|
||||
v-bind="modeOptions ? modeOptions[cMode] : null || {}"
|
||||
>
|
||||
<template v-slot="slot"><slot v-bind="slot" /></template>
|
||||
</component>
|
||||
</base-draganddrop>
|
||||
<bs-modal ref="modal" dialog-class="modal-lg" body-class="" @hidden-bs-modal="onModalHidden">
|
||||
<template #title>
|
||||
<slot v-if="modalEvent" v-bind="{mode: 'eventheader', event: modalEvent.event}" />
|
||||
</template>
|
||||
<template #default>
|
||||
<slot v-if="modalEvent" v-bind="{mode: 'event', event: modalEvent.event}" />
|
||||
</template>
|
||||
</bs-modal>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import DragAndDrop from '../../../helpers/DragAndDrop.js';
|
||||
|
||||
import CalDnd from '../../../directives/Calendar/DragAndDrop.js';
|
||||
|
||||
/**
|
||||
* TODO(chris): this needs serious rework!
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: "CalendarDragAndDrop",
|
||||
directives: {
|
||||
CalDnd
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
events: Vue.computed(() => this.correctedEvents),
|
||||
backgrounds: Vue.computed(() => this.backgrounds),
|
||||
dropAllowed: Vue.computed(() => this.dragging && this.dropAllowed)
|
||||
};
|
||||
},
|
||||
inject: {
|
||||
mode: "mode",
|
||||
dropableEvents: "dropableEvents"
|
||||
},
|
||||
props: {
|
||||
events: Array,
|
||||
backgrounds: Array
|
||||
},
|
||||
emits: [
|
||||
"drop"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
dragging: false,
|
||||
allowed: false,
|
||||
draggedInternalEvent: null,
|
||||
draggedExternalEvent: null,
|
||||
targetTimestamp: 0,
|
||||
targetGridEnds: null,
|
||||
dropAllowed: false,
|
||||
|
||||
shadowPreview: false // TODO(chris): IMPLEMENT! (use background instead of event as preview)
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
correctedEvents() {
|
||||
if (this.dragging) {
|
||||
if (this.draggedInternalEvent) {
|
||||
const index = this.events.findIndex(e => e.id == this.draggedInternalEvent.id);
|
||||
if (this.previewEvent && !this.shadowPreview)
|
||||
return this.events.toSpliced(index, 1, this.previewEvent);
|
||||
else
|
||||
return this.events.toSpliced(index, 1);
|
||||
}
|
||||
if (this.previewEvent && !this.shadowPreview)
|
||||
return [...this.events, this.previewEvent];
|
||||
}
|
||||
|
||||
return this.events;
|
||||
},
|
||||
correctedBackgrounds() {
|
||||
if (this.dragging) {
|
||||
if (this.shadowPreview) {
|
||||
// TODO(chris): how to get the length
|
||||
return [...this.backgrounds, {
|
||||
start: new Date(this.targetTimestamp),
|
||||
class: 'shadow-preview'
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
return this.backgrounds;
|
||||
},
|
||||
previewEvent() {
|
||||
if (!this.dragging || !this.dropAllowed)
|
||||
return null;
|
||||
if (!this.targetTimestamp)
|
||||
return null;
|
||||
|
||||
const event = this.draggedInternalEvent || this.draggedExternalEvent;
|
||||
|
||||
if (!event)
|
||||
return null;
|
||||
|
||||
// TODO(chris): calculate length correctly from orig
|
||||
let length = event.end - event.start;
|
||||
if (this.targetGridEnds)
|
||||
length = this.targetGridEnds.find(end => end >= this.targetTimestamp + length) - this.targetTimestamp;
|
||||
|
||||
return {
|
||||
orig: event.orig,
|
||||
start: this.targetTimestamp,
|
||||
end: this.targetTimestamp + length
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onDragstart(evt) {
|
||||
DragAndDrop.setTransferData(evt.detail.originalEvent, evt.detail.item.orig);
|
||||
this.draggedInternalEvent = evt.detail.item;
|
||||
},
|
||||
onDragend() {
|
||||
this.draggedInternalEvent = null;
|
||||
this.dragging = false;
|
||||
},
|
||||
onDragenter(evt) {
|
||||
this.dragging = true;
|
||||
|
||||
if (!this.draggedInternalEvent) {
|
||||
const event = DragAndDrop.getValidTransferData(evt.detail.originalEvent);
|
||||
if (event) {
|
||||
this.draggedExternalEvent = {
|
||||
id: event.id,
|
||||
type: event.type,
|
||||
start: event.isostart
|
||||
? luxon.DateTime.fromISO(event.isostart).setZone(this.timezone)
|
||||
: luxon.DateTime.local().setZone(this.timezone),
|
||||
end: event.isoend
|
||||
? luxon.DateTime.fromISO(event.isoend).setZone(this.timezone)
|
||||
: luxon.DateTime.local().setZone(this.timezone),
|
||||
orig: event
|
||||
};
|
||||
} else {
|
||||
this.draggedExternalEvent = null;
|
||||
}
|
||||
this.dropAllowed = this.dropableEvents(event, this.mode);
|
||||
} else {
|
||||
this.dropAllowed = this.dropableEvents(this.draggedInternalEvent, this.mode);
|
||||
}
|
||||
},
|
||||
onDragleave() {
|
||||
this.dragging = false;
|
||||
},
|
||||
onDragchange(evt) {
|
||||
this.targetTimestamp = evt.detail.timestamp;
|
||||
|
||||
this.targetGridEnds = evt.detail.ends || null;
|
||||
},
|
||||
onDrop(evt) {
|
||||
if (!this.dragging || !this.dropAllowed)
|
||||
return;
|
||||
|
||||
this.$emit('drop', evt, this.previewEvent.start, this.previewEvent.end);
|
||||
this.dropAllowed = false;
|
||||
this.dragging = false;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="fhc-calendar-base-draganddrop"
|
||||
@calendar-dragstart="onDragstart"
|
||||
@calendar-dragend="onDragend"
|
||||
v-cal-dnd:dropcage
|
||||
@calendar-dragenter="onDragenter"
|
||||
@calendar-dragleave="onDragleave"
|
||||
@calendar-dragchange="onDragchange"
|
||||
@drop="onDrop"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
import GridLine from './Grid/Line.js';
|
||||
import GridLineEvent from './Grid/Line/Event.js';
|
||||
|
||||
import CalDnd from '../../../directives/Calendar/DragAndDrop.js';
|
||||
|
||||
export default {
|
||||
name: "CalendarGrid",
|
||||
components: {
|
||||
GridLine,
|
||||
GridLineEvent
|
||||
},
|
||||
directives: {
|
||||
CalDnd
|
||||
},
|
||||
inject: {
|
||||
originalEvents: "events",
|
||||
originalBackgrounds: "backgrounds",
|
||||
dropAllowed: "dropAllowed"
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
flipAxis: Vue.computed(() => this.flipAxis),
|
||||
axisRow: Vue.computed(() => this.axisRow)
|
||||
};
|
||||
},
|
||||
props: {
|
||||
axisMain: {
|
||||
type: Array,
|
||||
required: true,
|
||||
validator(value) {
|
||||
return value.every(item => item instanceof luxon.DateTime);
|
||||
}
|
||||
},
|
||||
axisParts: {
|
||||
type: Array,
|
||||
required: true,
|
||||
validator(value) {
|
||||
return value.every(item =>
|
||||
item instanceof luxon.Duration
|
||||
|| Number.isInteger(item)
|
||||
|| (
|
||||
(
|
||||
item.start instanceof luxon.Duration
|
||||
|| Number.isInteger(item.start)
|
||||
) && (
|
||||
item.end instanceof luxon.Duration
|
||||
|| Number.isInteger(item.end)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
flipAxis: Boolean,
|
||||
allDayEvents: Boolean,
|
||||
axisMainCollapsible: Boolean,
|
||||
snapToGrid: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragging: false,
|
||||
resizeObserver: null,
|
||||
mutationObserver: null,
|
||||
userScroll: true
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
axisRow() {
|
||||
return this.flipAxis ? 'column' : 'row';
|
||||
},
|
||||
axisCol() {
|
||||
return this.flipAxis ? 'row' : 'column';
|
||||
},
|
||||
axisPartsWithBreaks() {
|
||||
return this.axisParts.reduce((res, tu, index) => {
|
||||
const start = tu.start || tu;
|
||||
const end = tu.end;
|
||||
|
||||
if (res.length) {
|
||||
const lastTuEnd = res.pop();
|
||||
if (Array.isArray(lastTuEnd)) {
|
||||
res.push({
|
||||
start: lastTuEnd[0],
|
||||
end: start,
|
||||
index: lastTuEnd[1]
|
||||
});
|
||||
} else if (lastTuEnd != start) {
|
||||
// add pause
|
||||
res.push({
|
||||
start: lastTuEnd,
|
||||
end: start
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!end) {
|
||||
res.push([start, index]);
|
||||
} else {
|
||||
res.push({
|
||||
start,
|
||||
end,
|
||||
index
|
||||
});
|
||||
res.push(end);
|
||||
}
|
||||
return res;
|
||||
}, []).slice(0, -1);
|
||||
},
|
||||
axisPartsSave() {
|
||||
if (!this.axisParts[this.axisParts.length - 1].end)
|
||||
return this.axisParts.slice(0, -1);
|
||||
return this.axisParts;
|
||||
},
|
||||
start() {
|
||||
return this.axisPartsWithBreaks[0].start;
|
||||
},
|
||||
end() {
|
||||
return this.axisPartsWithBreaks[this.axisPartsWithBreaks.length - 1].end;
|
||||
},
|
||||
ends() {
|
||||
const ends = [];
|
||||
const partsEnds = this.axisPartsWithBreaks
|
||||
.filter(p => p.index !== undefined)
|
||||
.map(p => p.end);
|
||||
for (var date of this.axisMain)
|
||||
for (var part of partsEnds)
|
||||
ends.push(date.plus(part));
|
||||
|
||||
return ends;
|
||||
},
|
||||
axisMainBorders() {
|
||||
return this.axisMain.reduce(
|
||||
(res, curr) => res.concat([curr.plus(this.start), curr.plus(this.end)]),
|
||||
[]
|
||||
);
|
||||
},
|
||||
eventsAllDay() {
|
||||
if (!this.allDayEvents)
|
||||
return [];
|
||||
return this.mapIntoMainAxis(this.originalEvents.filter(event => event.orig.allDayEvent));
|
||||
},
|
||||
eventsNormal() {
|
||||
if (!this.allDayEvents)
|
||||
return this.events;
|
||||
return this.mapIntoMainAxis(this.originalEvents.filter(event => !event.orig.allDayEvent));
|
||||
},
|
||||
events() {
|
||||
return this.mapIntoMainAxis(this.originalEvents);
|
||||
},
|
||||
backgrounds() {
|
||||
return this.mapIntoMainAxis(this.originalBackgrounds);
|
||||
},
|
||||
hasValidEvents() {
|
||||
return this.events.find(e => e.length);
|
||||
},
|
||||
styleGridCols() {
|
||||
let cols = 'repeat(' + this.axisMain.length + ', 1fr)';
|
||||
if (this.axisMainCollapsible) {
|
||||
if (this.hasValidEvents)
|
||||
cols = this.events
|
||||
.map(e => e.length
|
||||
? '1fr'
|
||||
: 'var(--fhc-calendar-axis-collapsible, .5fr)')
|
||||
.join(' ');
|
||||
}
|
||||
return cols;
|
||||
},
|
||||
styleGridRows() {
|
||||
const gridlines = {};
|
||||
|
||||
this.axisPartsWithBreaks.forEach(part => {
|
||||
let ts = part.start.toMillis();
|
||||
if (!gridlines[ts])
|
||||
gridlines[ts] = ['t_' + ts];
|
||||
if (part.index !== undefined)
|
||||
gridlines[ts].push('ps_' + part.index);
|
||||
ts = part.end.toMillis();
|
||||
if (!gridlines[ts])
|
||||
gridlines[ts] = ['t_' + ts];
|
||||
if (part.index !== undefined)
|
||||
gridlines[ts].push('pe_' + part.index);
|
||||
});
|
||||
|
||||
this.eventsNormal.forEach((events, mainIndex) => {
|
||||
let day = this.axisMain[mainIndex];
|
||||
events.forEach(event => {
|
||||
if (!event.startsHere && !event.endsHere)
|
||||
return;
|
||||
|
||||
if (event.startsHere) {
|
||||
let ts = event.start.diff(day).toMillis();
|
||||
if (!gridlines[ts])
|
||||
gridlines[ts] = ['t_' + ts, 'e_' + ts];
|
||||
}
|
||||
if (event.endsHere) {
|
||||
let ts = event.end.diff(day).toMillis();
|
||||
if (!gridlines[ts])
|
||||
gridlines[ts] = ['t_' + ts, 'e_' + ts];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Object.keys(gridlines).sort((a,b) => parseInt(a)-parseInt(b)).map((start, i, keys) => {
|
||||
let end = keys[i + 1];
|
||||
if (!end) {
|
||||
gridlines[start].push('end');
|
||||
return '[' + gridlines[start].join(' ') + ']';
|
||||
}
|
||||
return '[' + gridlines[start].join(' ') + '] ' + (end - start) + 'fr';
|
||||
}).join(' ');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
mapIntoMainAxis(target) {
|
||||
const result = Array.from({length: this.axisMain.length}, () => Array());
|
||||
|
||||
target.forEach(event => {
|
||||
const start = event.start || this.axisMainBorders[0].plus(-1);
|
||||
const end = event.end || this.axisMainBorders[this.axisMainBorders.length - 1].plus(1);
|
||||
|
||||
for (var i = 0; i < this.axisMain.length; i++) {
|
||||
let laneStart = this.axisMainBorders[i * 2];
|
||||
let laneEnd = this.axisMainBorders[i * 2 + 1];
|
||||
if (event.orig?.allDayEvent) {
|
||||
laneStart = laneStart.startOf('day');
|
||||
laneEnd = laneEnd.endOf('day');
|
||||
}
|
||||
if (start < laneEnd && end > laneStart) {
|
||||
const startsHere = start >= laneStart;
|
||||
const endsHere = end <= laneEnd;
|
||||
result[i].push({
|
||||
...event,
|
||||
startsHere,
|
||||
endsHere
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/* DRAG AND DROP */
|
||||
getPageTop(el) {
|
||||
let pageTop = el.offsetTop;
|
||||
if (el.offsetParent)
|
||||
pageTop += this.getPageTop(el.offsetParent);
|
||||
return pageTop;
|
||||
},
|
||||
getPageLeft(el) {
|
||||
let pageLeft = el.offsetLeft;
|
||||
if (el.offsetParent)
|
||||
pageLeft += this.getPageLeft(el.offsetParent);
|
||||
return pageLeft;
|
||||
},
|
||||
getTimestampFromMouse(evt, dayTimestamp) {
|
||||
let mouse, mouseFrac;
|
||||
if (this.flipAxis) {
|
||||
mouse = evt.pageX - this.getPageLeft(this.$refs.body) + this.$refs.main.scrollLeft;
|
||||
mouseFrac = mouse / this.$refs.body.offsetWidth;
|
||||
} else {
|
||||
mouse = evt.pageY - this.getPageTop(this.$refs.body) + this.$refs.main.scrollTop;
|
||||
mouseFrac = mouse / this.$refs.body.offsetHeight;
|
||||
}
|
||||
|
||||
return dayTimestamp + this.start + Math.floor((this.end - this.start) * mouseFrac);
|
||||
},
|
||||
|
||||
/* SCROLLING */
|
||||
enableAutoScroll() {
|
||||
if (!this.resizeObserver)
|
||||
this.resizeObserver = new ResizeObserver(this.scrollToEarliestEvent);
|
||||
this.resizeObserver.observe(this.$refs.body);
|
||||
|
||||
if (!this.mutationObserver)
|
||||
this.mutationObserver = new MutationObserver(mutations => {
|
||||
if (mutations.some(m => m.addedNodes.length && [].some.call(m.addedNodes, el => el.matches && el.matches('.fhc-calendar-base-grid-line-event'))))
|
||||
this.scrollToEarliestEvent();
|
||||
});
|
||||
this.mutationObserver.observe(this.$refs.body, {
|
||||
subtree: true,
|
||||
childList: true
|
||||
});
|
||||
|
||||
this.scrollToEarliestEvent();
|
||||
},
|
||||
disableAutoScroll() {
|
||||
if (this.resizeObserver)
|
||||
this.resizeObserver.disconnect();
|
||||
this.resizeObserver = null;
|
||||
|
||||
if (this.mutationObserver)
|
||||
this.mutationObserver.disconnect();
|
||||
this.mutationObserver = null;
|
||||
},
|
||||
scrollToEarliestEvent() {
|
||||
const eventElements = this.$refs.scroller.querySelectorAll('.fhc-calendar-base-grid-line-event');
|
||||
|
||||
let earliestEventOffset = [0, null];
|
||||
for (var el of eventElements.values()) {
|
||||
const top = el.offsetTop;
|
||||
if (!earliestEventOffset[1] || top < earliestEventOffset[0])
|
||||
earliestEventOffset = [top, el];
|
||||
}
|
||||
|
||||
this.userScroll = false;
|
||||
if (earliestEventOffset[1]) {
|
||||
earliestEventOffset[1].scrollIntoView({ behavior: "smooth" });
|
||||
} else {
|
||||
this.$refs.scroller.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.disableAutoScroll();
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
class="fhc-calendar-base-grid"
|
||||
style="display:grid;width:100%;height:100%"
|
||||
:style="'grid-template-' + axisRow + 's:auto' + (allDayEvents ? ' auto ' : ' ') + '1fr;grid-template-' + axisCol + 's:auto ' + styleGridCols"
|
||||
>
|
||||
<div
|
||||
class="grid-header"
|
||||
style="display:grid"
|
||||
:style="'grid-template-' + axisCol + 's:subgrid;grid-' + axisCol + ':1/-1'"
|
||||
>
|
||||
<div
|
||||
v-for="(date, index) in axisMain"
|
||||
:key="index"
|
||||
class="main-header"
|
||||
:class="{'collapsed-header': axisMainCollapsible && hasValidEvents && !events[index].length}"
|
||||
:style="'grid-' + axisCol + ':' + (2+index)"
|
||||
>
|
||||
<slot name="main-header" v-bind="{ index, date }" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="allDayEvents"
|
||||
class="grid-allday"
|
||||
style="display:grid"
|
||||
:style="'grid-template-' + axisCol + 's:subgrid;grid-' + axisCol + ':1/-1'"
|
||||
>
|
||||
<div
|
||||
v-for="(events, index) in eventsAllDay"
|
||||
:key="index"
|
||||
class="all-day-events"
|
||||
:style="'grid-' + axisCol + ':' + (2+index)"
|
||||
>
|
||||
<grid-line-event
|
||||
v-for="(event, i) in events"
|
||||
:key="i"
|
||||
:event="event"
|
||||
>
|
||||
<template v-slot="slot">
|
||||
<slot name="event" v-bind="slot" />
|
||||
</template>
|
||||
</grid-line-event>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref="scroller"
|
||||
@scrollend="userScroll ? disableAutoScroll() : userScroll = true"
|
||||
style="display:grid;overflow:auto"
|
||||
:style="'grid-' + axisCol + ':1/-1;grid-template-' + axisCol + 's:subgrid'"
|
||||
>
|
||||
<div
|
||||
ref="main"
|
||||
class="grid-main"
|
||||
style="position:relative;grid-column:1/-1;grid-row:1/-1;display:grid"
|
||||
:style="'grid-template-' + axisCol + 's:subgrid;grid-template-' + axisRow + 's:' + styleGridRows"
|
||||
>
|
||||
<div
|
||||
v-for="(part, index) in axisPartsSave"
|
||||
:key="index"
|
||||
class="part-header"
|
||||
:style="'grid-' + axisCol + ':1;grid-' + axisRow + ': ps_' + index + '/pe_' + index"
|
||||
>
|
||||
<slot name="part-header" v-bind="{ index, part }" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref="body"
|
||||
class="grid-body"
|
||||
style="display:grid;grid-template-rows:subgrid;grid-template-columns:subgrid"
|
||||
:style="'grid-' + axisCol + ':2/-1;grid-' + axisRow + ':1/-1'"
|
||||
v-cal-dnd:dropcage
|
||||
@calendar-dragenter="dragging = true"
|
||||
@calendar-dragleave="dragging = false"
|
||||
@dragover="dropAllowed ? $event.preventDefault() : null"
|
||||
>
|
||||
<template
|
||||
v-for="(date, index) in axisMain"
|
||||
:key="index"
|
||||
>
|
||||
<div
|
||||
v-for="(part, i) in axisPartsSave"
|
||||
:key="i"
|
||||
class="part-body"
|
||||
style="position:relative"
|
||||
:style="'grid-' + axisCol + ':' + (1+index) + ';grid-' + axisRow + ':ps_' + i + '/pe_' + i"
|
||||
>
|
||||
<slot name="part-body" v-bind="{ index, part }" />
|
||||
<div
|
||||
v-if="snapToGrid && dragging"
|
||||
style="position:absolute;inset:0;z-index:1"
|
||||
v-cal-dnd:dropzone.once="{date: date.plus(part.start || part), ends: ends.slice(ends.findIndex(end => end > date))}"
|
||||
></div>
|
||||
</div>
|
||||
<grid-line
|
||||
:start="date.plus(start)"
|
||||
:end="date.plus(end)"
|
||||
:date="date"
|
||||
:events="eventsNormal[index]"
|
||||
:backgrounds="backgrounds[index]"
|
||||
style="position:relative"
|
||||
:style="'grid-' + axisRow + ':1/-1;grid-' + axisCol + ':' + (1+index)"
|
||||
>
|
||||
<template #event="slot">
|
||||
<slot name="event" v-bind="slot" />
|
||||
</template>
|
||||
<template #dropzone>
|
||||
<div
|
||||
v-if="!snapToGrid && dragging"
|
||||
style="position:absolute;inset:0;z-index:1"
|
||||
v-cal-dnd:dropzone="evt => getTimestampFromMouse(evt, date)"
|
||||
></div>
|
||||
</template>
|
||||
</grid-line>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import LineEvent from './Line/Event.js';
|
||||
import LineBackground from './Line/Background.js';
|
||||
|
||||
/**
|
||||
* TODO(chris):
|
||||
* Event overflow for Month mode (more-button)
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: "GridLine",
|
||||
components: {
|
||||
LineEvent,
|
||||
LineBackground
|
||||
},
|
||||
inject: {
|
||||
axisRow: "axisRow"
|
||||
},
|
||||
props: {
|
||||
date: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
start: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
end: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
events: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
backgrounds: {
|
||||
type: Array,
|
||||
default: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
eventsWithRowInfo() {
|
||||
const events = [];
|
||||
this.events.forEach(event => {
|
||||
const rows = [1, -1];
|
||||
if (event.startsHere) {
|
||||
rows[0] = 't_' + event.start.diff(this.date).toMillis();
|
||||
}
|
||||
if (event.endsHere) {
|
||||
rows[1] = 't_' + event.end.diff(this.date).toMillis();
|
||||
}
|
||||
|
||||
events.push({
|
||||
...event,
|
||||
rows
|
||||
});
|
||||
});
|
||||
return events;
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
class="fhc-calendar-base-grid-line"
|
||||
style="position:relative;display:grid;grid-auto-flow:dense"
|
||||
:style="'grid-template-' + axisRow + 's:subgrid'"
|
||||
>
|
||||
<line-background
|
||||
v-for="bg in backgrounds"
|
||||
:start="start"
|
||||
:end="end"
|
||||
:background="bg"
|
||||
></line-background>
|
||||
<line-event
|
||||
v-for="(event, i) in eventsWithRowInfo"
|
||||
:key="i"
|
||||
:style="'grid-' + axisRow + ': ' + event.rows.join('/')"
|
||||
:event="event"
|
||||
>
|
||||
<template v-slot="slot">
|
||||
<slot name="event" v-bind="slot" />
|
||||
</template>
|
||||
</line-event>
|
||||
<slot name="dropzone" />
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
export default {
|
||||
name: "GridLineBackground",
|
||||
inject: {
|
||||
flipAxis: "flipAxis"
|
||||
},
|
||||
props: {
|
||||
start: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
end: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
background: {
|
||||
type: Object,
|
||||
required: true,
|
||||
validator(value) {
|
||||
if (!value.start && !value.end)
|
||||
return false;
|
||||
if (value.start && !(value.start instanceof luxon.DateTime))
|
||||
return false;
|
||||
if (value.end && !(value.end instanceof luxon.DateTime))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
styles() {
|
||||
if (!this.background.endsHere && !this.background.startsHere)
|
||||
return this.background.style;
|
||||
|
||||
const perc = (this.end.ts - this.start.ts) / 100;
|
||||
|
||||
let border = {};
|
||||
if (this.background.startsHere)
|
||||
border[this.flipAxis ? 'left' : 'top'] = (this.background.start.diff(this.start)) / perc + '%';
|
||||
if (this.background.endsHere)
|
||||
border[this.flipAxis ? 'right' : 'bottom'] = (this.end.diff(this.background.end)) / perc + '%';
|
||||
|
||||
if (!this.background.style)
|
||||
return border;
|
||||
|
||||
return [this.background.style, border];
|
||||
},
|
||||
classes() {
|
||||
if (!this.background.endsHere && !this.background.startsHere)
|
||||
return this.background.class;
|
||||
|
||||
const result = [];
|
||||
if (this.background.class)
|
||||
result.push(this.background.class);
|
||||
if (this.background.startsHere)
|
||||
result.push('bg-begin');
|
||||
if (this.background.endsHere)
|
||||
result.push('bg-end');
|
||||
return result;
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
class="fhc-calendar-base-grid-line-background"
|
||||
:class="classes"
|
||||
style="position:absolute;inset:0;z-index:0"
|
||||
:style="styles"
|
||||
:title="background.title"
|
||||
>
|
||||
<span v-if="background.label">{{ background.label }}</span>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import CalDnd from '../../../../../directives/Calendar/DragAndDrop.js';
|
||||
import CalClick from '../../../../../directives/Calendar/Click.js';
|
||||
|
||||
export default {
|
||||
name: "GridLineEvent",
|
||||
directives: {
|
||||
CalDnd,
|
||||
CalClick
|
||||
},
|
||||
inject: {
|
||||
draggableEvents: "draggableEvents",
|
||||
mode: "mode"
|
||||
},
|
||||
props: {
|
||||
event: {
|
||||
type: Object,
|
||||
required: true,
|
||||
validator(value) {
|
||||
return (value.start && value.end && value.orig);
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isHeaderOrFooter() {
|
||||
return ['header', 'footer'].includes(this.event.orig);
|
||||
},
|
||||
draggable() {
|
||||
return !this.isHeaderOrFooter && this.draggableEvents(this.event.orig, this.mode);
|
||||
},
|
||||
classes() {
|
||||
const classes = [];
|
||||
if (this.isHeaderOrFooter) {
|
||||
classes.push('event-' + this.event.orig);
|
||||
} else {
|
||||
if (this.event.startsHere)
|
||||
classes.push('event-begin');
|
||||
if (this.event.endsHere)
|
||||
classes.push('event-end');
|
||||
}
|
||||
return classes
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
class="fhc-calendar-base-grid-line-event event"
|
||||
:class="classes"
|
||||
style="z-index: 1"
|
||||
:draggable="draggable"
|
||||
v-cal-dnd:draggable="event"
|
||||
v-cal-click:event="isHeaderOrFooter ? event : event.orig"
|
||||
>
|
||||
<slot :event="isHeaderOrFooter ? event : event.orig">
|
||||
{{ event.orig }}
|
||||
</slot>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* TODO(chris): use click-directive
|
||||
*/
|
||||
import DatePicker from './Header/Datepicker.js';
|
||||
|
||||
export default {
|
||||
name: "CalendarHeader",
|
||||
components: {
|
||||
DatePicker
|
||||
},
|
||||
props: {
|
||||
date: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
modeOptions: {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
btnMonth: Boolean,
|
||||
btnWeek: Boolean,
|
||||
btnDay: Boolean,
|
||||
btnList: Boolean
|
||||
},
|
||||
emits: [
|
||||
"next",
|
||||
"prev",
|
||||
"click:mode",
|
||||
"update:date",
|
||||
"update:mode"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
open: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
clickMode(evt, mode) {
|
||||
this.$emit('click:mode', evt);
|
||||
if (!evt.defaultPrevented)
|
||||
this.$emit('update:mode', mode);
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="fhc-calendar-base-header">
|
||||
<div class="header-actions">
|
||||
<div class="header-userdefined">
|
||||
<slot />
|
||||
</div>
|
||||
<div class="header-modes">
|
||||
<div class="d-flex gap-1 justify-content-end" role="group">
|
||||
<button
|
||||
v-if="btnMonth"
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
:class="{active: mode === 'month'}"
|
||||
@click="clickMode($event, 'month')"
|
||||
>
|
||||
<i class="fa fa-calendar-days"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="btnWeek"
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
:class="{active: mode === 'week'}"
|
||||
@click="clickMode($event, 'week')"
|
||||
>
|
||||
<i class="fa fa-calendar-week"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="btnDay"
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
:class="{active: mode === 'day'}"
|
||||
@click="clickMode($event, 'day')"
|
||||
>
|
||||
<i class="fa fa-calendar-day"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="btnList"
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
:class="{active: mode === 'list'}"
|
||||
@click="clickMode($event, 'list')"
|
||||
>
|
||||
<i class="fa fa-table-list"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-picker">
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
class="btn btn-outline-secondary border-0"
|
||||
@click="$emit('prev')"
|
||||
:disabled="open"
|
||||
>
|
||||
<i class="fa fa-chevron-left"></i>
|
||||
</button>
|
||||
<date-picker
|
||||
:mode="mode"
|
||||
:date="date"
|
||||
@update:date="$emit('update:date', $event)"
|
||||
@open="open = true"
|
||||
@closed="open = false"
|
||||
:list-length="modeOptions.length"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-outline-secondary border-0"
|
||||
@click="$emit('next')"
|
||||
:disabled="open"
|
||||
>
|
||||
<i class="fa fa-chevron-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// TODO(chris): translate aria-labels
|
||||
|
||||
export default {
|
||||
name: "CalendarHeaderDatepicker",
|
||||
components: {
|
||||
VueDatePicker
|
||||
},
|
||||
inject: [
|
||||
"locale",
|
||||
"timezone"
|
||||
],
|
||||
props: {
|
||||
date: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
listLength: {
|
||||
type: Number,
|
||||
default: 7
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
"update:date"
|
||||
],
|
||||
computed: {
|
||||
convertedDate() {
|
||||
// convert to target TZ then strip TZ Information
|
||||
// so the datepicker can work with local times
|
||||
return this.date.setZone(this.timezone).setZone('local', { keepLocalTime: true });
|
||||
},
|
||||
current() {
|
||||
switch (this.mode) {
|
||||
case "month":
|
||||
return {month: this.convertedDate.month-1, year: this.convertedDate.year};
|
||||
case "list":
|
||||
return [this.convertedDate.startOf('day').ts, this.convertedDate.startOf('day').plus({ days: this.listLength }).ts - 1];
|
||||
case "week":
|
||||
return [this.convertedDate.startOf('week', { useLocaleWeeks: true }).ts, this.convertedDate.endOf('week', { useLocaleWeeks: true }).ts];
|
||||
case "day":
|
||||
return this.convertedDate;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
title() {
|
||||
switch (this.mode) {
|
||||
case "month":
|
||||
return this.date.toLocaleString({ month: 'long', year: 'numeric' });
|
||||
case "week":
|
||||
var year = this.date.localWeekYear;
|
||||
var week = this.date.toFormat('nn');
|
||||
return this.$p.t('calendar/year_kw', { year, week });
|
||||
case "list":
|
||||
return this.date.toLocaleString(luxon.DateTime.DATE_FULL) + '-' + this.date.plus({ days: this.listLength - 1 }).toLocaleString(luxon.DateTime.DATE_FULL);
|
||||
case "day":
|
||||
return this.date.toLocaleString(luxon.DateTime.DATE_FULL);
|
||||
default:
|
||||
return 'View not Supported';
|
||||
}
|
||||
},
|
||||
weekStart() {
|
||||
return luxon.Info.getStartOfWeek(this.date)%7;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
update(value) {
|
||||
let date;
|
||||
switch (this.mode) {
|
||||
case "month":
|
||||
value.month++;
|
||||
date = luxon.DateTime.fromObject(value).setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
|
||||
break;
|
||||
case "list":
|
||||
case "week":
|
||||
date = luxon.DateTime.fromJSDate(value[0]).setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
|
||||
break;
|
||||
case "day":
|
||||
date = luxon.DateTime.fromJSDate(value).setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
|
||||
break;
|
||||
default:
|
||||
return; // Don't update if the value is invalid!
|
||||
}
|
||||
this.$emit("update:date", date);
|
||||
},
|
||||
weekNumbers(date) {
|
||||
return luxon.DateTime.fromJSDate(date, { locale: this.locale }).localWeekNumber;
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<vue-date-picker
|
||||
:model-value="current"
|
||||
@update:model-value="update"
|
||||
:format="() => title"
|
||||
:month-picker="mode == 'month'"
|
||||
:week-picker="mode == 'week'"
|
||||
:range="mode == 'list' ? { autoRange: listLength - 1 } : false"
|
||||
:text-input="mode == 'day'"
|
||||
:week-start="weekStart"
|
||||
:week-numbers="{ type: weekNumbers }"
|
||||
:clearable="false"
|
||||
:enable-time-picker="false"
|
||||
:config="{ keepActionRow: mode != 'month' }"
|
||||
:action-row="{ showSelect: false, showCancel: false, showNow: mode != 'month', showPreview: false }"
|
||||
auto-apply
|
||||
six-weeks
|
||||
teleport
|
||||
:locale="locale"
|
||||
:now-button-label="$p.t('calendar/today')"
|
||||
:week-num-name="$p.t('calendar/kw')"
|
||||
/>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import CalClick from '../../../../directives/Calendar/Click.js';
|
||||
|
||||
export default {
|
||||
name: "LabelDay",
|
||||
directives: {
|
||||
CalClick
|
||||
},
|
||||
props: {
|
||||
date: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
titleFull() {
|
||||
return this.date.toLocaleString({day: 'numeric', month: 'long', year: 'numeric'});
|
||||
},
|
||||
titleLong() {
|
||||
return this.date.toLocaleString({day: '2-digit', month: '2-digit', year: 'numeric'});
|
||||
},
|
||||
titleShort() {
|
||||
return this.date.toLocaleString({day: 'numeric', month: 'numeric'});
|
||||
},
|
||||
titleNarrow() {
|
||||
return this.date.toLocaleString({day: 'numeric'});
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
class="fhc-calendar-base-label-day"
|
||||
v-cal-click:day="date"
|
||||
>
|
||||
<span class="full">{{ titleFull }}</span>
|
||||
<span class="long">{{ titleLong }}</span>
|
||||
<span class="short">{{ titleShort }}</span>
|
||||
<span class="narrow">{{ titleNarrow }}</span>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import CalClick from '../../../../directives/Calendar/Click.js';
|
||||
|
||||
export default {
|
||||
name: "LabelDow",
|
||||
directives: {
|
||||
CalClick
|
||||
},
|
||||
props: {
|
||||
date: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
titleLong() {
|
||||
return this.date.toLocaleString({weekday: 'long'});
|
||||
},
|
||||
titleShort() {
|
||||
return this.date.toLocaleString({weekday: 'short'});
|
||||
},
|
||||
titleNarrow() {
|
||||
return this.date.toLocaleString({weekday: 'narrow'});
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
class="fhc-calendar-base-label-dow"
|
||||
v-cal-click:dow="date"
|
||||
>
|
||||
<b class="long">{{ titleLong }}</b>
|
||||
<b class="short">{{ titleShort }}</b>
|
||||
<b class="narrow">{{ titleNarrow }}</b>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
export default {
|
||||
name: "LabelTime",
|
||||
props: {
|
||||
part: {
|
||||
type: [luxon.Duration, Number, Object],
|
||||
required: true,
|
||||
validator(value) {
|
||||
if (value instanceof Object) {
|
||||
if (value instanceof luxon.Duration)
|
||||
return true;
|
||||
let start_ok = true;
|
||||
let end_ok = true;
|
||||
if (value.start) {
|
||||
start_ok = (
|
||||
value.start instanceof luxon.Duration
|
||||
|| Number.isInteger(value.start)
|
||||
);
|
||||
}
|
||||
if (value.end) {
|
||||
end_ok = (
|
||||
value.end instanceof luxon.Duration
|
||||
|| Number.isInteger(value.end)
|
||||
);
|
||||
}
|
||||
return start_ok && end_ok;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sanitizedTimestamps() {
|
||||
return this.part.start || this.part.end ? this.part : { start: this.part };
|
||||
},
|
||||
start() {
|
||||
if (!this.sanitizedTimestamps.start)
|
||||
return null;
|
||||
return this.formatTime(this.sanitizedTimestamps.start);
|
||||
},
|
||||
end() {
|
||||
if (!this.sanitizedTimestamps.end)
|
||||
return null;
|
||||
return this.formatTime(this.sanitizedTimestamps.end);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatTime(date) {
|
||||
return date.toISOTime({ suppressSeconds: true });
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-base-label-time">
|
||||
<span v-if="start">{{ start }}</span>
|
||||
<span v-if="end">-</span>
|
||||
<span v-if="end">{{ end }}</span>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import CalClick from '../../../../directives/Calendar/Click.js';
|
||||
|
||||
export default {
|
||||
name: "LabelWeek",
|
||||
directives: {
|
||||
CalClick
|
||||
},
|
||||
props: {
|
||||
date: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
weeks() {
|
||||
const firstDay = this.date.startOf('week', { useLocaleWeeks: true });
|
||||
const lastDay = this.date.endOf('week', { useLocaleWeeks: true });
|
||||
|
||||
const weeks = [
|
||||
{ number: firstDay.localWeekNumber, year: firstDay.localWeekYear },
|
||||
{ number: lastDay.localWeekNumber, year: lastDay.localWeekYear }
|
||||
];
|
||||
if (weeks[0].number == weeks[1].number)
|
||||
weeks.pop();
|
||||
return weeks;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-base-label-week">
|
||||
<span
|
||||
v-for="week in weeks"
|
||||
v-cal-click:week="week"
|
||||
>
|
||||
{{ week.number }}
|
||||
</span>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
export default {
|
||||
name: 'CalendarSlider',
|
||||
inject: {
|
||||
time: {
|
||||
from: "sliderTime",
|
||||
default: ".3s"
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
'slid'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
target: 0,
|
||||
extrasAfter: 0,
|
||||
extrasBefore: 0,
|
||||
running: false,
|
||||
promiseResolve: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
itemsAfter() {
|
||||
return [...Array(this.extrasAfter)].map((i, k) => 1+k);
|
||||
},
|
||||
itemsBefore() {
|
||||
return [...Array(this.extrasBefore)].map((i, k) => k-this.extrasBefore);
|
||||
},
|
||||
styleSlider() {
|
||||
const style = {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
};
|
||||
if (this.running) {
|
||||
style.left = (-this.target * 100) + '%';
|
||||
style.transition = 'left ' + this.time + ' ease-in-out';
|
||||
}
|
||||
return style;
|
||||
},
|
||||
styleBefore() {
|
||||
return {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
right: '100%',
|
||||
width: (this.extrasBefore * 100) + '%'
|
||||
};
|
||||
},
|
||||
styleAfter() {
|
||||
return {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
left: '100%',
|
||||
width: (this.extrasAfter * 100) + '%'
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
prevPage() {
|
||||
return this.slidePages(-1);
|
||||
},
|
||||
nextPage() {
|
||||
return this.slidePages(1);
|
||||
},
|
||||
slidePages(dir) {
|
||||
return new Promise(resolve => {
|
||||
this.promiseResolve = resolve;
|
||||
this.running = true;
|
||||
const newTarget = this.target + dir;
|
||||
if (newTarget > 0) {
|
||||
if (this.extrasAfter < newTarget)
|
||||
this.extrasAfter = newTarget;
|
||||
} else if (newTarget < 0) {
|
||||
if (-this.extrasBefore > newTarget)
|
||||
this.extrasBefore = -newTarget;
|
||||
}
|
||||
this.target = newTarget;
|
||||
});
|
||||
},
|
||||
endSlide() {
|
||||
if (this.promiseResolve) {
|
||||
this.promiseResolve(this.target);
|
||||
this.promiseResolve = null;
|
||||
}
|
||||
this.$emit('slid', this.target);
|
||||
this.running = false;
|
||||
this.target = 0;
|
||||
this.extrasAfter = this.extrasBefore = 0;
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
class="fhc-calendar-base-slider h-100"
|
||||
style="position:relative;overflow:hidden"
|
||||
>
|
||||
<div
|
||||
:style="styleSlider"
|
||||
@transitionend="endSlide"
|
||||
>
|
||||
<div :style="styleBefore">
|
||||
<div
|
||||
v-for="i in itemsBefore"
|
||||
:key="i"
|
||||
style="height:100%;width:100%"
|
||||
>
|
||||
<slot :offset="i" />
|
||||
</div>
|
||||
</div>
|
||||
<div :style="styleAfter">
|
||||
<div
|
||||
v-for="i in itemsAfter"
|
||||
:key="i"
|
||||
style="height:100%;width:100%"
|
||||
>
|
||||
<slot :offset="i" />
|
||||
</div>
|
||||
</div>
|
||||
<div style="height:100%;width:100%">
|
||||
<slot :offset="0" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
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 CalendarDay from './Day.js';
|
||||
import CalendarMinimized from './Minimized.js';
|
||||
import CalendarDate from '../../composables/CalendarDate.js';
|
||||
import CalendarDates from '../../composables/CalendarDates.js';
|
||||
|
||||
const todayDate = new Date(new Date().setHours(0, 0, 0, 0));
|
||||
const today = todayDate.getTime()
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CalendarMonth,
|
||||
CalendarMonths,
|
||||
CalendarYears,
|
||||
CalendarWeek,
|
||||
CalendarWeeks,
|
||||
CalendarDay,
|
||||
CalendarMinimized,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
today,
|
||||
todayDate,
|
||||
date: this.date,
|
||||
focusDate: this.focusDate,
|
||||
size: Vue.computed({ get: () => this.size }),
|
||||
containerHeight: Vue.computed({ get: () => this.containerHeight }),
|
||||
containerWidth: Vue.computed({ get: () => this.containerWidth }),
|
||||
|
||||
events: Vue.computed(() => this.eventsPerDay),
|
||||
filteredEvents: Vue.computed(() => this.filteredEvents),
|
||||
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),
|
||||
mode: Vue.computed(()=>this.mode),
|
||||
selectedEvent: Vue.computed(() => this.selectedEvent),
|
||||
setSelectedEvent: (event)=>{this.selectedEvent = event;},
|
||||
};
|
||||
},
|
||||
props: {
|
||||
events: Array,
|
||||
initialDate: {
|
||||
type: [Date, String],
|
||||
default: new Date()
|
||||
},
|
||||
showWeeks: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
initialMode: {
|
||||
type: String,
|
||||
default: 'month'
|
||||
},
|
||||
minimized: Boolean,
|
||||
noWeekView: Boolean,
|
||||
noMonthView: Boolean
|
||||
},
|
||||
watch:{
|
||||
mode(newVal) {
|
||||
this.$emit('change:mode', newVal)
|
||||
},
|
||||
selectedEvent:{
|
||||
handler(newSelectedEvent) {
|
||||
this.$emit('selectedEvent', newSelectedEvent);
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
// scroll to the first event if the html element was found
|
||||
scrollTime({focusDate,scrollTime}){
|
||||
// return early if the scrollTime is not set
|
||||
if(!scrollTime) return;
|
||||
// scroll the Stundenplan to the closest event
|
||||
Vue.nextTick(()=>{
|
||||
let previousScrollAnchor = document.getElementById('scroll' + (scrollTime - 1) + this.focusDate.d + this.focusDate.w)
|
||||
let scrollAnchor = document.getElementById('scroll' + scrollTime + this.focusDate.d + this.focusDate.w);
|
||||
if (previousScrollAnchor) {
|
||||
previousScrollAnchor.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
else {
|
||||
if (scrollAnchor) {
|
||||
scrollAnchor.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
'select:day',
|
||||
'select:event',
|
||||
'change:range',
|
||||
'update:minimized',
|
||||
'selectedEvent',
|
||||
'change:offset'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
header: '',
|
||||
prevMode: null,
|
||||
currMode: null,
|
||||
date: new CalendarDate(),
|
||||
focusDate: new CalendarDate(),
|
||||
size: 0,
|
||||
containerWidth: 0,
|
||||
containerHeight: 0,
|
||||
selectedEvent:null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sizeClass() {
|
||||
// mainly determines calendar font-size
|
||||
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;
|
||||
}, {});
|
||||
},
|
||||
// returns the hour of the earliest event, used to scroll to the events in the calendar (week / day view)
|
||||
scrollTime() {
|
||||
// return the first beginning time of the filtered events
|
||||
if(this.filteredEvents && Array.isArray(this.filteredEvents) && this.filteredEvents.length > 0)
|
||||
{
|
||||
let scrollTime = parseInt(this.filteredEvents.sort((a, b) => parseInt(a.beginn) - parseInt(b.beginn))[0].beginn);
|
||||
// to ensure that the scrollTime watcher triggers even if the scrollTime doesn't change, it returns both the scrollTime and the focusDate
|
||||
return { focusDate: this.focusDate, scrollTime };
|
||||
}
|
||||
// there is no event that matches the current view mode constraints
|
||||
else
|
||||
{
|
||||
return { focusDate: this.focusDate, scrollTime: null };
|
||||
}
|
||||
},
|
||||
// filters the events based on the current calendar view mode
|
||||
// week view - filter events based on their week
|
||||
// day view - filter events based on their day and week
|
||||
// month view - does not filter the events
|
||||
filteredEvents: function(){
|
||||
if (this.events && Array.isArray(this.events) && this.events.length > 0) {
|
||||
let filteredEvents = this.events.filter(event => {
|
||||
let eventDate = new CalendarDate(new Date(event.datum));
|
||||
if (this.mode == 'week' || this.mode == 'Week')
|
||||
{
|
||||
// week view filters the elements only for the same week
|
||||
return this.focusDate.w == eventDate.w;
|
||||
}
|
||||
else if (this.mode == 'day' || this.mode == 'Day')
|
||||
{
|
||||
// day view filters the elements for the same day and the same week
|
||||
return this.focusDate.d == eventDate.d && this.focusDate.w == eventDate.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
// returns all the events, does not filter the events
|
||||
return true;
|
||||
}
|
||||
})
|
||||
|
||||
return filteredEvents;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setMode(mode) {
|
||||
this.mode = mode
|
||||
},
|
||||
handleInput(day) {
|
||||
this.$emit(day[0], day[1]);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
const initMode = this.initialMode.toLowerCase()
|
||||
const allowedInitialModes = ['day'];
|
||||
if (!this.noWeekView)
|
||||
allowedInitialModes.push('week');
|
||||
if (!this.noMonthView)
|
||||
allowedInitialModes.push('month');
|
||||
this.mode = allowedInitialModes[allowedInitialModes.indexOf(initMode)] || allowedInitialModes.pop();
|
||||
this.date.set(new Date(this.initialDate));
|
||||
this.focusDate.set(this.date);
|
||||
},
|
||||
mounted() {
|
||||
if (this.$refs.container) {
|
||||
new ResizeObserver(entries => {
|
||||
for (const entry of entries) {
|
||||
const w = entry.contentBoxSize ? entry.contentBoxSize[0].inlineSize : entry.contentRect.width;
|
||||
const h = entry.contentBoxSize ? entry.contentBoxSize[0].blockSize : entry.contentRect.height;
|
||||
|
||||
// https://getbootstrap.com/docs/5.0/layout/breakpoints/
|
||||
// bootstrap breakpoints watch window size and this function monitors container size of calendar itself.
|
||||
// calendar is using bootstrap breakpoints which influence layout, which retriggers this function
|
||||
// -> some width constellations will loop so we dont use values around bs5 breakpoints
|
||||
// ['xs', 'sm', 'md', 'lg'][this.size]
|
||||
if (w >= 600)
|
||||
this.size = 3;
|
||||
else if (w >= 350)
|
||||
this.size = 2;
|
||||
else if (w >= 250)
|
||||
this.size = 1;
|
||||
else
|
||||
this.size = 0;
|
||||
|
||||
this.containerWidth = w
|
||||
this.containerHeight = h
|
||||
}
|
||||
}).observe(this.$refs.container);
|
||||
}
|
||||
},
|
||||
unmounted(){
|
||||
CalendarDates.cleanup();
|
||||
},
|
||||
template: /*html*/`
|
||||
<div ref="container" class="fhc-calendar card h-100" :class="sizeClass">
|
||||
<component :is="'calendar-' + mode" @updateMode="mode = $event" @change:range="$emit('change:range',$event)"
|
||||
@input="handleInput" @change:offset="$emit('change:offset', $event)">
|
||||
<template #calendarDownloads>
|
||||
<slot name="calendarDownloads" ></slot>
|
||||
</template>
|
||||
<template #monthPage="{event,day}">
|
||||
<slot name="monthPage" :event="event" :day="day" ></slot>
|
||||
</template>
|
||||
<template #weekPage="{event,day}">
|
||||
<slot name="weekPage" :event="event" :day="day" ></slot>
|
||||
</template>
|
||||
<template #dayPage="{event,day,mobile}">
|
||||
<slot name="dayPage" :event="event" :day="day" :mobile="mobile"></slot>
|
||||
</template>
|
||||
<template #pageMobilContent="{lvMenu}">
|
||||
<slot name="pageMobilContent" :lvMenu="lvMenu"></slot>
|
||||
</template>
|
||||
<template #pageMobilContentEmpty>
|
||||
<slot name="pageMobilContentEmpty" ></slot>
|
||||
</template>
|
||||
<template #minimizedPage="{event,day}">
|
||||
<slot name="minimizedPage" :event="event" :day="day"></slot>
|
||||
</template>
|
||||
</component>
|
||||
</div>`
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import CalendarAbstract from './Abstract.js';
|
||||
import CalendarPane from './Pane.js';
|
||||
import CalendarDayPage from './Day/Page.js';
|
||||
import CalendarDate from '../../composables/CalendarDate.js';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
CalendarAbstract
|
||||
],
|
||||
components: {
|
||||
CalendarDayPage,
|
||||
CalendarPane
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return this.focusDate.wYear + ' KW ' + this.focusDate.w;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
paneChanged(dir) {
|
||||
let previousDate = new CalendarDate(this.focusDate);
|
||||
this.focusDate.d += dir;
|
||||
this.emitRangeChanged(previousDate);
|
||||
},
|
||||
emitRangeChanged(previousDate) {
|
||||
this.$emit('change:range', { start: previousDate, end:this.focusDate });
|
||||
},
|
||||
prev() {
|
||||
this.$refs.pane.prev();
|
||||
this.$emit('change:offset', { y: 0, m: 0, d: -1 });
|
||||
},
|
||||
next() {
|
||||
this.$refs.pane.next();
|
||||
this.$emit('change:offset', { y: 0, m: 0, d: 1 });
|
||||
},
|
||||
selectEvent(event) {
|
||||
this.$emit('input', ['select:event', event]);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.emitRangeChanged();
|
||||
},
|
||||
template: /*html*/`
|
||||
<div class="fhc-calendar-day">
|
||||
<calendar-header :title="title" @prev="prev" @next="next" @updateMode="$emit('updateMode', $event)" @click="$emit('updateMode', 'week')">
|
||||
<template #calendarDownloads>
|
||||
<slot name="calendarDownloads"></slot>
|
||||
</template>
|
||||
</calendar-header>
|
||||
<calendar-pane ref="pane" v-slot="slot" @slid="paneChanged">
|
||||
<calendar-day-page :active="slot.active" :year="focusDate.y" :week="focusDate.w+slot.offset" @updateMode="$emit('updateMode', $event)" @page:back="prev" @page:forward="next" @input="selectEvent" >
|
||||
<template #dayPage="{event,day,mobile}">
|
||||
<slot name="dayPage" :event="event" :day="day" :mobile="mobile" ></slot>
|
||||
</template>
|
||||
<template #pageMobilContent="{lvMenu}">
|
||||
<slot name="pageMobilContent" :lvMenu="lvMenu" ></slot>
|
||||
</template>
|
||||
<template #pageMobilContentEmpty>
|
||||
<slot name="pageMobilContentEmpty" ></slot>
|
||||
</template>
|
||||
</calendar-day-page>
|
||||
</calendar-pane>
|
||||
</div>`
|
||||
}
|
||||
@@ -1,434 +0,0 @@
|
||||
import CalendarDate from '../../../composables/CalendarDate.js';
|
||||
import LvModal from "../../../components/Cis/Mylv/LvModal.js";
|
||||
|
||||
import ApiStundenplan from '../../../api/factory/stundenplan.js';
|
||||
import ApiAddons from '../../../api/factory/addons.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 {
|
||||
name: 'DayPage',
|
||||
components: {
|
||||
LvModal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hourPosition: null,
|
||||
curHourPosition: null,
|
||||
hourPositionTime: null,
|
||||
lvMenu: null,
|
||||
}
|
||||
},
|
||||
inject: [
|
||||
'today',
|
||||
'todayDate',
|
||||
'date',
|
||||
'focusDate',
|
||||
'size',
|
||||
'events',
|
||||
'noMonthView',
|
||||
'filteredEvents',
|
||||
'isSliding',
|
||||
'calendarScrollTop',
|
||||
'calendarClientHeight',
|
||||
'setSelectedEvent',
|
||||
'selectedEvent',
|
||||
'rowMinHeight'
|
||||
],
|
||||
props: {
|
||||
year: Number,
|
||||
week: Number,
|
||||
active: Boolean,
|
||||
},
|
||||
emits: [
|
||||
'updateMode',
|
||||
'page:back',
|
||||
'page:forward',
|
||||
'input'
|
||||
],
|
||||
watch: {
|
||||
//TODO: on first render non of the day-page components are active and the watcher on selectedEvent does not fetch the lvMenu
|
||||
//TODO: workaround is to watch the active state and refetch in case the lvMenu is empty
|
||||
active: {
|
||||
handler(value) {
|
||||
if (value) {
|
||||
if (!this.lvMenu) {
|
||||
this.fetchLvMenu(this.selectedEvent);
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
eventsPerDayAndHour: {
|
||||
handler(newEvents) {
|
||||
// if no event is selected, select the first event of the day
|
||||
if (this.selectedEvent == null && newEvents[this.day.toDateString()]?.events.length > 0) {
|
||||
let events = newEvents[this.day.toDateString()]?.events;
|
||||
if (Array.isArray(events) && events.length > 0) {
|
||||
this.setSelectedEvent(events[0].orig);
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
selectedEvent: {
|
||||
handler(event) {
|
||||
// return early if the day-page component is not the active carousel item
|
||||
if (!this.active) {
|
||||
return;
|
||||
}
|
||||
this.lvMenu = null;
|
||||
this.fetchLvMenu(event);
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
isSliding: {
|
||||
handler(value) {
|
||||
if (value) {
|
||||
this.setSelectedEvent(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
overlayStyle() {
|
||||
return {
|
||||
'background-color': '#F5E9D7',
|
||||
'position': 'absolute',
|
||||
'pointer-events': 'none',
|
||||
'z-index': 2,
|
||||
height: this.getDayTimePercent + '%',
|
||||
opacity: 0.5,
|
||||
overflow: 'hidden'
|
||||
}
|
||||
},
|
||||
pageHeaderStyle() {
|
||||
return {
|
||||
'z-index': 4,
|
||||
'grid-template-columns': 'repeat(' + this.day.length + ', 1fr)',
|
||||
'grid-template-rows': 1,
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
}
|
||||
},
|
||||
dayText(){
|
||||
if(!this.day)return {};
|
||||
return {
|
||||
heading: this.day.toLocaleString(this.$p.user_locale.value, { dateStyle: 'short' }),
|
||||
tag: this.day.toLocaleString(this.$p.user_locale.value, { weekday: this.size < 2 ? 'narrow' : (this.size < 3 ? 'short' : 'long') }),
|
||||
datum: this.day.toLocaleString(this.$p.user_locale.value, [{ day: 'numeric', month: 'numeric' }, { day: 'numeric', month: 'numeric' }, { day: 'numeric', month: 'numeric' }, { dateStyle: 'short' }][this.size]),
|
||||
}
|
||||
},
|
||||
noLvStyle() {
|
||||
return {
|
||||
top: (this.calendarScrollTop + 100) + 'px',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
'text-align': 'center',
|
||||
width: '100%',
|
||||
'z-index': 1,
|
||||
}
|
||||
},
|
||||
indicatorStyle() {
|
||||
return {
|
||||
'pointer-events': 'none',
|
||||
'padding-left': '3.5rem',
|
||||
'margin-top': '-1px',
|
||||
'z-index': 2,
|
||||
'border-color': '#00649C!important',
|
||||
top: this.hourPosition + 'px',
|
||||
left: 0,
|
||||
right: 0,
|
||||
}
|
||||
},
|
||||
curTime() {
|
||||
const now = new Date();
|
||||
return String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0');
|
||||
},
|
||||
curIndicatorStyle() {
|
||||
return {
|
||||
'pointer-events': 'none',
|
||||
'padding-left': '7rem',
|
||||
'margin-top': '-1px',
|
||||
'z-index': 2,
|
||||
'border-color': '#00649C!important',
|
||||
top: this.getDayTimePercent + '%',
|
||||
left: 0,
|
||||
right: 0,
|
||||
}
|
||||
},
|
||||
noEventsCondition() {
|
||||
return !this.isSliding && (this.filteredEvents?.length === 0 || !this.filteredEvents);
|
||||
},
|
||||
hours() {
|
||||
// returns an array with elements starting at 7 and ending at 24
|
||||
return [...Array(24).keys()].filter(hour => hour >= 7 && hour <= 24);
|
||||
},
|
||||
day() {
|
||||
return new Date(this.focusDate.y, this.focusDate.m, this.focusDate.d);
|
||||
},
|
||||
eventsPerDayAndHour() {
|
||||
// return early if the calendar pane is sliding
|
||||
if (this.isSliding) return {};
|
||||
|
||||
const res = {};
|
||||
|
||||
let key = this.day.toDateString();
|
||||
|
||||
let nextDay = new Date(this.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 < this.day ? this.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];
|
||||
},
|
||||
lookingAtToday() {
|
||||
return this.date.compare(this.todayDate)
|
||||
},
|
||||
getDayTimePercent() {
|
||||
const now = new Date(Date.now())
|
||||
const currentMinutes = now.getMinutes() + now.getHours() * 60
|
||||
let timePercentage = ((currentMinutes - (this.hours[0] * 60)) / (this.hours.length * 60)) * 100;
|
||||
|
||||
return timePercentage
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
dayGridStyle(day) {
|
||||
const styleObj = {
|
||||
'grid-template-columns': '1 1fr',
|
||||
'grid-template-rows': 'repeat(' + (this.hours.length * 60 / this.smallestTimeFrame) + ', 1fr)',
|
||||
}
|
||||
|
||||
if(this.date.compare(this.todayDate)) {
|
||||
styleObj['backgroundImage'] = 'linear-gradient(to bottom, #F5E9D7 '+this.getDayTimePercent+'%, #FFFFFF '+this.getDayTimePercent+'%)'
|
||||
styleObj['border-color'] = '#E8E8E8';
|
||||
// styleObj.opacity = 0.5; // would opaque the whole column
|
||||
}
|
||||
|
||||
return styleObj
|
||||
},
|
||||
fetchLvMenu(event) {
|
||||
if (event && event.type == 'lehreinheit') {
|
||||
this.$api
|
||||
.call(ApiStundenplan.getLehreinheitStudiensemester(event.lehreinheit_id[0]))
|
||||
.then(res => res.data)
|
||||
.then(studiensemester_kurzbz => this.$api.call(
|
||||
ApiAddons.getLvMenu(
|
||||
event.lehreinheit_id,
|
||||
studiensemester_kurzbz
|
||||
)
|
||||
))
|
||||
.then(res => {
|
||||
if (res.data) {
|
||||
this.lvMenu = res.data;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
hourGridIdentifier(hour) {
|
||||
// this is the id attribute that is responsible to scroll the calender to the first event
|
||||
return 'scroll' + hour + this.focusDate.d + this.week;
|
||||
},
|
||||
hourGridStyle(hour) {
|
||||
return {
|
||||
'pointer-events': 'none',
|
||||
top: this.getAbsolutePositionForHour(hour),
|
||||
left: 0,
|
||||
right: 0,
|
||||
'z-index': 0,
|
||||
}
|
||||
},
|
||||
eventGridStyle(day, event) {
|
||||
return {
|
||||
'z-index': 1,
|
||||
'grid-column-start': 1 + (event.lane - 1) * day.lanes / event.maxLane,
|
||||
'grid-column-end': 1 + event.lane * day.lanes / event.maxLane,
|
||||
'grid-row-start': this.dateToMinutesOfDay(event.start),
|
||||
'grid-row-end': this.dateToMinutesOfDay(event.end),
|
||||
'background-color': event.orig.color,
|
||||
'--test': this.dateToMinutesOfDay(event.end),
|
||||
}
|
||||
},
|
||||
eventClick(evt) {
|
||||
let event = evt.orig;
|
||||
this.setSelectedEvent(event);
|
||||
this.$emit('input', event);
|
||||
},
|
||||
calcHourPosition(event) {
|
||||
let height = this.$refs.eventcontainer.getBoundingClientRect().height;
|
||||
let top = this.$refs.eventcontainer.getBoundingClientRect().top;
|
||||
let position = event.clientY - top;
|
||||
// position percentage of total height
|
||||
let timePercentage = (position / height) * 100;
|
||||
// minute percentage of total minutes
|
||||
let result = (this.hours.length * 60) * (timePercentage / 100);
|
||||
// calculate time in float
|
||||
let currentMinutes = ((result + (this.hours[0] * 60)) / 60);
|
||||
// get hour part of time
|
||||
let currentHour = Math.floor(currentMinutes);
|
||||
// get float part of time
|
||||
let minutePercentage = currentMinutes % currentHour;
|
||||
// calculate minutes from float part of time
|
||||
let minute = Math.round(60 * minutePercentage);
|
||||
// convert minutes to 5 minute interval
|
||||
if (minute % 5 != 0) {
|
||||
minute = Math.round(minute / 5) * 5;
|
||||
}
|
||||
// in case the rounding made the minutes 60, increase the hour and reset the minutes
|
||||
if (minute == 60) {
|
||||
currentHour++;
|
||||
minute = 0;
|
||||
}
|
||||
|
||||
// ## after rounding the time to the nearest 5 Minute interval, we have to convert the time back to the relative position
|
||||
// convert current time in minutes
|
||||
currentMinutes = currentHour * 60 + minute;
|
||||
// calculate the minutes percentage of the total minutes
|
||||
timePercentage = ((currentMinutes - (this.hours[0] * 60)) / (this.hours.length * 60)) * 100;
|
||||
// calculate the relative position of the time percentage
|
||||
position = height * (timePercentage / 100);
|
||||
this.hourPosition = position;
|
||||
|
||||
// add padding to minutes that consist of only one digit
|
||||
minute.toString().length == 1 ? minute = "0" + minute : minute;
|
||||
this.hourPositionTime = currentHour + ":" + minute;
|
||||
},
|
||||
getAbsolutePositionForHour(hour) {
|
||||
// used for the absolute positioning of the gutters of hours
|
||||
return (100 / this.hours.length) * (hour - (24 - this.hours.length)) + '%';
|
||||
},
|
||||
changeToMonth(day) {
|
||||
if (!this.noMonthView) {
|
||||
this.date.set(day);
|
||||
this.focusDate.set(day);
|
||||
this.$emit('updateMode', 'month');
|
||||
}
|
||||
},
|
||||
dateToMinutesOfDay(day) {
|
||||
return Math.floor(((day.getHours() - 7) * 60 + day.getMinutes()) / this.smallestTimeFrame) + 1;
|
||||
}
|
||||
|
||||
},
|
||||
template: /*html*/`
|
||||
<div class="fhc-calendar-day-page h-100">
|
||||
<div class="row m-0 h-100">
|
||||
<div class="col-12 col-xl-6 p-0 h-100">
|
||||
<div class="d-flex flex-column h-100">
|
||||
<div ref="header" class="fhc-calendar-week-page-header d-grid border-2 border-bottom text-center" :style="pageHeaderStyle">
|
||||
<div type="button" class="flex-grow-1" :title="dayText.heading" @click.prevent="changeToMonth(day)">
|
||||
<div class="fw-bold">{{dayText.tag}}</div>
|
||||
<a href="#" class="small text-secondary text-decoration-none" >{{dayText.datum}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="scroll g-0" style="height: 100%; overflow-y: scroll;">
|
||||
|
||||
<div ref="eventcontainer" class="position-relative flex-grow-1" @mousemove="calcHourPosition" @mouseleave="hourPosition = null" >
|
||||
<div :id="hourGridIdentifier(hour)" v-for="hour in hours" :key="hour" class="position-absolute box-shadow-border-top" :style="hourGridStyle(hour)"></div>
|
||||
|
||||
<Transition>
|
||||
<div v-if="hourPosition && !noEventsCondition" class="position-absolute border-top small" :style="indicatorStyle">
|
||||
<span class="border border-top-0 px-2 bg-white">{{hourPositionTime}}</span>
|
||||
</div>
|
||||
</Transition>
|
||||
<Transition>
|
||||
<div v-if="lookingAtToday && !noEventsCondition" class="position-absolute border-top small" :style="curIndicatorStyle">
|
||||
<span class="border border-top-0 px-2 bg-white">{{curTime}}</span>
|
||||
</div>
|
||||
</Transition>
|
||||
<div>
|
||||
<h1 v-if="noEventsCondition" class="m-0 text-secondary" ref="noEventsText" :style="noLvStyle">{{ $p.t('lehre/noLvFound') }}</h1>
|
||||
<div :class="{'fhc-calendar-no-events-overlay':noEventsCondition, 'events':true}">
|
||||
|
||||
<div class="hours">
|
||||
<div v-for="hour in hours" :style="'min-height:' + rowMinHeight " :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="dayGridStyle(day)">
|
||||
<div v-if="lookingAtToday && !noEventsCondition" :style="overlayStyle"></div>
|
||||
<div v-for="event in day.events" :key="event" :style="eventGridStyle(day,event)" v-contrast
|
||||
:selected="event.orig == selectedEvent" class="fhc-entry mx-2 small rounded overflow-hidden" >
|
||||
<!-- desktop version of the page template, parent receives slotProp mobile = false -->
|
||||
<div class="d-none d-xl-block h-100 " @click.prevent="eventClick(event)">
|
||||
<slot name="dayPage" :event="event" :day="day" :mobile="false">
|
||||
<p>this is a slot placeholder</p>
|
||||
</slot>
|
||||
</div>
|
||||
<!-- mobile version of the page template, parent receives slotProp mobile = true -->
|
||||
<div class="d-block d-xl-none h-100" @click.prevent="eventClick(event)">
|
||||
<slot name="dayPage" :event="event" :day="day" :mobile="true">
|
||||
<p>this is a slot placeholder</p>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-xl-block col-xl-6 p-4 d-none" style="max-height: 100%">
|
||||
<div style="z-index:0; max-height: 100%" class="sticky-top d-flex justify-content-center align-items-center flex-column">
|
||||
<div style="max-height: 100%; overflow-y:auto;" class="w-100">
|
||||
<template v-if="selectedEvent && lvMenu">
|
||||
<slot name="pageMobilContent" :lvMenu="lvMenu" >
|
||||
<p>this is a slot placeholder</p>
|
||||
</slot>
|
||||
</template>
|
||||
<template v-else-if="noEventsCondition">
|
||||
<slot name="pageMobilContentEmpty" >
|
||||
<h3>This is an slot placeholder</h3>
|
||||
</slot>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="p-4 d-flex w-100 justify-content-center align-items-center">
|
||||
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
`
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
export default {
|
||||
data(){
|
||||
return{
|
||||
selected: this.mode,
|
||||
modes:{
|
||||
day: { mode_bezeichnung: "day", icon: "fa-calendar-day" , condition:true},
|
||||
week: { mode_bezeichnung: "week", icon: "fa-calendar-week", condition: !this.noWeekView },
|
||||
month: { mode_bezeichnung: "month", icon: "fa-calendar-days", condition: !this.noMonthView },
|
||||
},
|
||||
headerPadding:null,
|
||||
}
|
||||
},
|
||||
inject: [
|
||||
'eventsAreNull',
|
||||
'size',
|
||||
'mode',
|
||||
'noWeekView',
|
||||
'noMonthView',
|
||||
'containerWidth'
|
||||
],
|
||||
props: {
|
||||
title: String
|
||||
},
|
||||
emits: [
|
||||
'updateMode',
|
||||
'prev',
|
||||
'next',
|
||||
'click'
|
||||
],
|
||||
computed: {
|
||||
getHeaderClassSide() {
|
||||
return this.containerWidth > 780 ? 'col-3' : 'col-12'
|
||||
},
|
||||
getHeaderClassMiddle() {
|
||||
return this.containerWidth > 780 ? 'col-6' : 'col-12'
|
||||
}
|
||||
},
|
||||
template: /*html*/`
|
||||
<div class="calendar-header card-header w-100">
|
||||
<div class="row align-items-center ">
|
||||
<div :class="getHeaderClassSide" class="d-flex justify-content-center justify-content-md-start align-items-center">
|
||||
<slot name="calendarDownloads"></slot>
|
||||
</div>
|
||||
<div :class="getHeaderClassMiddle" :style="{'padding-left':headerPadding}">
|
||||
<div class="row align-items-center justify-content-center">
|
||||
<div class="col-auto p-2">
|
||||
<button class="btn btn-outline-secondary border-0" :class="{'btn-sm':!this.size}" @click="$emit('prev')"><i class="fa fa-chevron-left"></i></button>
|
||||
</div>
|
||||
<div class="justify-content-center text-center col-auto">
|
||||
<div class="d-flex justify-content-center align-items-center">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto p-2">
|
||||
<button class="btn btn-outline-secondary border-0" :class="{'btn-sm': !this.size}" @click="$emit('next')"><i class="fa fa-chevron-right"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="viewButtons" v-if="!noWeekView && !noMonthView" :class="getHeaderClassSide" class="d-flex justify-content-center justify-content-md-end align-items-center" style="pointer-events: none;">
|
||||
<div style="pointer-events: all;">
|
||||
<div role="group" aria-label="Kalender Modus">
|
||||
<button type="button" :class="{'active':mode_kurzbz.toLowerCase() === mode.toLowerCase()}" style="margin-right: 4px;" @click.prevent="$emit('updateMode',mode_kurzbz)" class="btn btn-outline-secondary" v-for="({mode_bezeichnung,icon,condition},mode_kurzbz) in modes">
|
||||
<i v-if="condition" class="fa" :class="icon" ></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
import FhcCalendar from "./Base.js";
|
||||
|
||||
import ApiLvPlan from '../../api/factory/lvPlan.js';
|
||||
|
||||
import { useEventLoader } from '../../composables/EventLoader.js';
|
||||
|
||||
import ModeDay from './Mode/Day.js';
|
||||
import ModeWeek from './Mode/Week.js';
|
||||
import ModeMonth from './Mode/Month.js';
|
||||
|
||||
export default {
|
||||
name: "CalendarLvPlan",
|
||||
components: {
|
||||
FhcCalendar
|
||||
},
|
||||
inject: [
|
||||
"renderers"
|
||||
],
|
||||
props: {
|
||||
timezone: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
date: {
|
||||
type: [Date, String, Number, luxon.DateTime],
|
||||
default: luxon.DateTime.local()
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'Week'
|
||||
},
|
||||
getPromiseFunc: {
|
||||
type: Function,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
"update:date",
|
||||
"update:mode",
|
||||
"update:range"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
modes: {
|
||||
day: Vue.markRaw(ModeDay),
|
||||
week: Vue.markRaw(ModeWeek),
|
||||
month: Vue.markRaw(ModeMonth)
|
||||
},
|
||||
modeOptions: {
|
||||
day: {
|
||||
emptyMessage: Vue.computed(() => this.$p.t('lehre/noLvFound')),
|
||||
emptyMessageDetails: Vue.computed(() => this.$p.t('lehre/noLvFound'))
|
||||
},
|
||||
week: {
|
||||
collapseEmptyDays: false
|
||||
}
|
||||
},
|
||||
teachingunits: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
backgrounds() {
|
||||
let now = luxon.DateTime.now().setZone(this.timezone);
|
||||
|
||||
if (this.mode == 'Month')
|
||||
return [
|
||||
{
|
||||
class: 'background-past',
|
||||
end: now.startOf('day')
|
||||
}
|
||||
];
|
||||
|
||||
return [
|
||||
{
|
||||
class: 'background-past',
|
||||
end: now,
|
||||
label: now.startOf('minute').toISOTime({ suppressSeconds: true, includeOffset: false })
|
||||
}
|
||||
];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
eventStyle(event) {
|
||||
if (!event.farbe)
|
||||
return undefined;
|
||||
return '--event-bg:#' + event.farbe;
|
||||
},
|
||||
updateRange(rangeInterval) {
|
||||
this.rangeInterval = rangeInterval;
|
||||
this.$emit('update:range', rangeInterval);
|
||||
}
|
||||
},
|
||||
setup(props, context) {
|
||||
const rangeInterval = Vue.ref(null);
|
||||
|
||||
const { events, lv } = useEventLoader(rangeInterval, props.getPromiseFunc);
|
||||
|
||||
Vue.watch(lv, newValue => {
|
||||
context.emit('update:lv', newValue);
|
||||
});
|
||||
|
||||
return {
|
||||
rangeInterval,
|
||||
events,
|
||||
lv
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.$api
|
||||
.call(ApiLvPlan.getStunden())
|
||||
.then(res => {
|
||||
return this.teachingunits = res.data.map(el => ({
|
||||
id: el.stunde,
|
||||
start: el.beginn,
|
||||
end: el.ende
|
||||
}));
|
||||
});
|
||||
},
|
||||
template: /* html */`
|
||||
<fhc-calendar
|
||||
ref="calendar"
|
||||
class="fhc-calendar-lvplan"
|
||||
:date="date"
|
||||
:modes="modes"
|
||||
:mode-options="modeOptions"
|
||||
:mode="mode"
|
||||
:timezone="timezone"
|
||||
:locale="$p.user_locale.value"
|
||||
:events="events || []"
|
||||
:backgrounds="backgrounds"
|
||||
:time-grid="teachingunits"
|
||||
show-btns
|
||||
@update:date="(newDate, newMode) => $emit('update:date', newDate, newMode)"
|
||||
@update:mode="(newMode, newDate) => $emit('update:mode', newMode, newDate)"
|
||||
@update:range="updateRange"
|
||||
>
|
||||
<template v-slot="{ event, mode }">
|
||||
<div
|
||||
:class="'event-type-' + event.type + ' ' + mode + 'PageContainer'"
|
||||
:type="mode == 'day' ? 'button' : undefined"
|
||||
:style="eventStyle(event)"
|
||||
>
|
||||
<component
|
||||
v-if="mode == 'event'"
|
||||
:is="renderers[event.type]?.modalContent"
|
||||
:event="event"
|
||||
></component>
|
||||
<component
|
||||
v-else-if="mode == 'eventheader'"
|
||||
:is="renderers[event.type]?.modalTitle"
|
||||
:event="event"
|
||||
></component>
|
||||
<component
|
||||
v-else
|
||||
:is="renderers[event.type]?.calendarEvent"
|
||||
:event="event"
|
||||
></component>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<slot />
|
||||
</template>
|
||||
</fhc-calendar>`
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import CalendarAbstract from './Abstract.js';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
CalendarAbstract
|
||||
],
|
||||
inject: [
|
||||
'size',
|
||||
'minimized',
|
||||
'date',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
start: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
maximize() {
|
||||
this.minimized = false;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-minimized h-100 d-flex flex-column">
|
||||
<slot name="minimizedPage"></slot>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import BaseSlider from '../Base/Slider.js';
|
||||
import DayView from './Day/View.js';
|
||||
|
||||
export default {
|
||||
name: "ModeDay",
|
||||
components: {
|
||||
BaseSlider,
|
||||
DayView
|
||||
},
|
||||
props: {
|
||||
currentDate: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
"update:currentDate",
|
||||
"update:range",
|
||||
"click",
|
||||
"requestModalOpen",
|
||||
"requestModalClose"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
focusDate: this.currentDate,
|
||||
rangeOffset: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
range() {
|
||||
let first = this.focusDate.startOf('day');
|
||||
let last = this.focusDate.endOf('day');
|
||||
|
||||
if (this.rangeOffset != 0) {
|
||||
if (this.rangeOffset < 0) {
|
||||
first = first.plus({ days: this.rangeOffset });
|
||||
} else {
|
||||
last = last.plus({ days: this.rangeOffset });
|
||||
}
|
||||
}
|
||||
|
||||
return luxon.Interval.fromDateTimes(first, last);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentDate() {
|
||||
this.rangeOffset = this.currentDate.startOf('day').diff(this.focusDate.startOf('day'), 'days').days;
|
||||
if (this.rangeOffset) {
|
||||
this.$refs.view.$refs.grid.disableAutoScroll();
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.slidePages(this.rangeOffset).then(this.updatePage);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
prevPage() {
|
||||
this.rangeOffset = this.$refs.slider.target - 1;
|
||||
this.$refs.view.$refs.grid.disableAutoScroll();
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.prevPage().then(this.updatePage);
|
||||
},
|
||||
nextPage() {
|
||||
this.rangeOffset = this.$refs.slider.target + 1;
|
||||
this.$refs.view.$refs.grid.disableAutoScroll();
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.nextPage().then(this.updatePage);
|
||||
},
|
||||
updatePage(days) {
|
||||
const newFocusDate = this.focusDate.plus({ days });
|
||||
this.focusDate = newFocusDate;
|
||||
this.rangeOffset = 0;
|
||||
this.$emit('update:currentDate', this.focusDate);
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.view.$refs.grid.enableAutoScroll();
|
||||
},
|
||||
viewAttrs(days) {
|
||||
const day = this.focusDate.plus({ days });
|
||||
return { ...this.$attrs, day };
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.view.$refs.grid.enableAutoScroll();
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="fhc-calendar-mode-day flex-grow-1 position-relative"
|
||||
>
|
||||
<base-slider ref="slider" v-slot="slot">
|
||||
<day-view
|
||||
ref="view"
|
||||
v-bind="viewAttrs(slot.offset)"
|
||||
@request-modal-open="$emit('requestModalOpen', $event)"
|
||||
@request-modal-close="$emit('requestModalClose', $event)"
|
||||
>
|
||||
<template v-slot="slot"><slot v-bind="slot" /></template>
|
||||
</day-view>
|
||||
</base-slider>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
import CalendarGrid from '../../Base/Grid.js';
|
||||
import LabelDay from '../../Base/Label/Day.js';
|
||||
import LabelDow from '../../Base/Label/Dow.js';
|
||||
import LabelTime from '../../Base/Label/Time.js';
|
||||
|
||||
import { useResizeObserver } from '../../../../composables/Responsive.js';
|
||||
|
||||
export default {
|
||||
name: "DayView",
|
||||
components: {
|
||||
CalendarGrid,
|
||||
LabelDay,
|
||||
LabelDow,
|
||||
LabelTime
|
||||
},
|
||||
inject: {
|
||||
timeGrid: "timeGrid",
|
||||
originalEvents: "events",
|
||||
timezone: "timezone"
|
||||
},
|
||||
props: {
|
||||
day: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
emptyMessage: String,
|
||||
emptyMessageDetails: String
|
||||
},
|
||||
emits: [
|
||||
"requestModalOpen",
|
||||
"requestModalClose"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
chosenEvent: null,
|
||||
gridMainRef: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
axisMain() {
|
||||
return [this.day.startOf('day')];
|
||||
},
|
||||
axisParts() {
|
||||
if (this.timeGrid) {
|
||||
// create {start, end} array
|
||||
return this.timeGrid.map(tu => {
|
||||
return {
|
||||
start: luxon.Duration.fromISOTime(tu.start),
|
||||
end: luxon.Duration.fromISOTime(tu.end)
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// create 07:00-23:00
|
||||
return Array.from({ length: 17 }, (e, i) => luxon.Duration.fromObject({ hours: i + 7 }));
|
||||
}
|
||||
},
|
||||
events() {
|
||||
return this.originalEvents
|
||||
.filter(event => event.start < this.day.plus({ days: 1 }) && event.end > this.day)
|
||||
.sort((a, b) => a.start.ts - b.start.ts)
|
||||
.map(evt => evt.orig);
|
||||
},
|
||||
currentEvent() {
|
||||
if (this.chosenEvent) {
|
||||
if (this.events.find(e => e == this.chosenEvent))
|
||||
return this.chosenEvent;
|
||||
}
|
||||
let first = null;
|
||||
if (this.events)
|
||||
first = this.events.find(Boolean); // undefined => none found
|
||||
|
||||
if (first && first.type == 'loading')
|
||||
return null; // null => loading
|
||||
|
||||
return first;
|
||||
},
|
||||
isToday() {
|
||||
return this.day.hasSame(luxon.DateTime.now().setZone(this.timezone), 'day');
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
compact() {
|
||||
if (this.compact) {
|
||||
if (this.chosenEvent) {
|
||||
this.$emit('requestModalOpen', {
|
||||
event: this.chosenEvent,
|
||||
closeFn: () => { this.chosenEvent = null; }
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.$emit('requestModalClose');
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClickDefaults(evt) {
|
||||
if (evt.detail.source == 'event') {
|
||||
this.chosenEvent = evt.detail.value;
|
||||
if (this.compact) {
|
||||
this.$emit('requestModalOpen', {
|
||||
event: this.chosenEvent,
|
||||
closeFn: () => { this.chosenEvent = null; }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const container = Vue.ref(null); // use useTemplateRef when updating to Vue 3.5
|
||||
const { compact } = useResizeObserver(container, 750);
|
||||
|
||||
return {
|
||||
container, // must be exposed or it won't be set in Vue < 3.5
|
||||
compact
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.gridMainRef = this.$refs.grid.$refs.main;
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
ref="container"
|
||||
class="fhc-calendar-mode-day-view d-flex h-100"
|
||||
@cal-click-default.capture="handleClickDefaults"
|
||||
>
|
||||
<calendar-grid
|
||||
ref="grid"
|
||||
:axis-main="axisMain"
|
||||
:axis-parts="axisParts"
|
||||
:snap-to-grid="!!timeGrid"
|
||||
all-day-events
|
||||
>
|
||||
<template #main-header="{ date }">
|
||||
<div :class="{ today: isToday }">
|
||||
<label-dow
|
||||
@cal-click="evt => evt.detail.source = 'day'"
|
||||
v-bind="{ date }"
|
||||
/>
|
||||
<label-day
|
||||
v-bind="{ date }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #part-header="{ part }">
|
||||
<label-time v-bind="{ part }" />
|
||||
</template>
|
||||
<template #event="slot">
|
||||
<div v-if="slot.event.type == 'loading'" class="placeholder-glow h-100 opacity-50">
|
||||
<span class="placeholder w-100 h-100" />
|
||||
</div>
|
||||
<slot v-else v-bind="slot" mode="day" />
|
||||
</template>
|
||||
</calendar-grid>
|
||||
<Teleport :disabled="!gridMainRef" :to="gridMainRef">
|
||||
<div
|
||||
v-if="emptyMessage && currentEvent !== null && !currentEvent"
|
||||
class="fhc-calendar-no-events-overlay"
|
||||
style="position:absolute;inset:0"
|
||||
>
|
||||
{{ emptyMessage }}
|
||||
</div>
|
||||
</Teleport>
|
||||
<div class="event-details" v-if="!compact">
|
||||
<div
|
||||
v-if="currentEvent === null"
|
||||
class="p-4 d-flex w-100 justify-content-center align-items-center"
|
||||
>
|
||||
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
|
||||
</div>
|
||||
<h3 v-else-if="!currentEvent">{{ emptyMessageDetails }}</h3>
|
||||
<slot v-else :event="currentEvent" mode="event" />
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import BaseSlider from '../Base/Slider.js';
|
||||
import ListView from './List/View.js';
|
||||
|
||||
export default {
|
||||
name: "ModeList",
|
||||
components: {
|
||||
BaseSlider,
|
||||
ListView
|
||||
},
|
||||
props: {
|
||||
currentDate: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
length: {
|
||||
type: Number,
|
||||
default: 7
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
"update:currentDate",
|
||||
"update:range",
|
||||
"click",
|
||||
"requestModalOpen"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
focusDate: this.currentDate,
|
||||
rangeOffset: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
range() {
|
||||
let first = this.focusDate;
|
||||
let last = first.plus({ days: this.length });
|
||||
|
||||
if (this.rangeOffset != 0) {
|
||||
if (this.rangeOffset < 0) {
|
||||
first = first.plus({ days: this.rangeOffset });
|
||||
} else {
|
||||
last = first.plus({ days: this.rangeOffset + this.length });
|
||||
}
|
||||
}
|
||||
|
||||
return luxon.Interval.fromDateTimes(first, last);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentDate() {
|
||||
this.rangeOffset = this.currentDate.startOf('day').diff(this.focusDate.startOf('day'), 'days').days;
|
||||
if (this.rangeOffset) {
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.slidePages(this.rangeOffset).then(this.updatePage);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
prevPage() {
|
||||
this.rangeOffset = this.$refs.slider.target - 1;
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.prevPage().then(this.updatePage);
|
||||
},
|
||||
nextPage() {
|
||||
this.rangeOffset = this.$refs.slider.target + 1;
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.nextPage().then(this.updatePage);
|
||||
},
|
||||
updatePage(offset) {
|
||||
const newFocusDate = this.focusDate.plus({ days: offset });
|
||||
this.focusDate = newFocusDate;
|
||||
this.rangeOffset = 0;
|
||||
this.$emit('update:currentDate', this.focusDate);
|
||||
this.$emit('update:range', this.range);
|
||||
},
|
||||
viewAttrs(offset) {
|
||||
const day = this.focusDate.plus({ days: offset });
|
||||
return { day, length: this.length };
|
||||
},
|
||||
handleClickDefaults(evt) {
|
||||
switch (evt.detail.source) {
|
||||
case 'event':
|
||||
// default: Request Modal
|
||||
this.$emit('requestModalOpen', { event: evt.detail.value });
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$emit('update:range', this.range);
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="fhc-calendar-mode-list flex-grow-1 position-relative"
|
||||
@cal-click-default.capture="handleClickDefaults"
|
||||
>
|
||||
<base-slider ref="slider" v-slot="slot">
|
||||
<list-view v-bind="viewAttrs(slot.offset)">
|
||||
<template v-slot="slot"><slot v-bind="slot" /></template>
|
||||
</list-view>
|
||||
</base-slider>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import LabelDay from '../../Base/Label/Day.js';
|
||||
import LabelDow from '../../Base/Label/Dow.js';
|
||||
|
||||
import CalDnd from '../../../../directives/Calendar/DragAndDrop.js';
|
||||
import CalClick from '../../../../directives/Calendar/Click.js';
|
||||
|
||||
// TODO(chris): drag and drop
|
||||
|
||||
export default {
|
||||
name: "ListView",
|
||||
components: {
|
||||
LabelDay,
|
||||
LabelDow
|
||||
},
|
||||
directives: {
|
||||
CalDnd,
|
||||
CalClick
|
||||
},
|
||||
inject: {
|
||||
draggableEvents: "draggableEvents",
|
||||
events: "events",
|
||||
timezone: "timezone"
|
||||
},
|
||||
props: {
|
||||
day: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
length: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chosenEvent: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
days() {
|
||||
return Array.from({ length: this.length }, (e, days) => this.day.plus({ days }));
|
||||
},
|
||||
eventsPerDay() {
|
||||
const eventsPerDay = this.days.map(day => {
|
||||
return {
|
||||
day,
|
||||
events: this.events
|
||||
.filter(event => event.start < day.plus({ days: 1 }) && event.end > day)
|
||||
.sort((a, b) => a.start.ts - b.start.ts)
|
||||
};
|
||||
});
|
||||
return eventsPerDay.filter(day => day.events.length);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
draggable(event) {
|
||||
return this.draggableEvents(event.orig, 'list');
|
||||
},
|
||||
isToday(date) {
|
||||
return date.hasSame(luxon.DateTime.now().setZone(this.timezone), 'day');
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="fhc-calendar-mode-list-view h-100 overflow-auto">
|
||||
<div v-if="!eventsPerDay.length" class="h-100">
|
||||
<slot :event="undefined" mode="list" />
|
||||
</div>
|
||||
<div v-for="{ day, events } in eventsPerDay">
|
||||
<div
|
||||
class="text-center"
|
||||
:class="{ today: isToday(day) }"
|
||||
>
|
||||
<label-dow
|
||||
:date="day"
|
||||
class="d-inline"
|
||||
@cal-click="evt => evt.detail.source = 'day'"
|
||||
/>
|
||||
,
|
||||
<label-day :date="day" class="d-inline" />
|
||||
</div>
|
||||
<div v-for="event in events">
|
||||
<div v-if="event.type == 'loading'" class="placeholder-glow opacity-50">
|
||||
<span class="placeholder w-100" />
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="event"
|
||||
:draggable="draggable(event)"
|
||||
v-cal-dnd:draggable="event"
|
||||
v-cal-click:event="event.orig"
|
||||
>
|
||||
<slot :event="event.orig" mode="list" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import BaseSlider from '../Base/Slider.js';
|
||||
import MonthView from './Month/View.js';
|
||||
|
||||
export default {
|
||||
name: "ModeMonth",
|
||||
components: {
|
||||
BaseSlider,
|
||||
MonthView
|
||||
},
|
||||
props: {
|
||||
currentDate: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
"update:currentDate",
|
||||
"update:range",
|
||||
"click",
|
||||
"requestModalOpen"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
focusDate: this.currentDate,
|
||||
rangeOffset: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
range() {
|
||||
let first = this.focusDate.startOf('month').startOf('week', { useLocaleWeeks: true });
|
||||
let last = first.plus({ days: 41 }).endOf('day'); // NOTE(chris): 6 weeks minus 1 day
|
||||
|
||||
if (this.rangeOffset != 0) {
|
||||
const nextFocusDate = this.focusDate.plus({ months: this.rangeOffset});
|
||||
const nextRangeStart = nextFocusDate.startOf('month').startOf('week', { useLocaleWeeks: true });
|
||||
if (this.rangeOffset < 0) {
|
||||
first = nextRangeStart;
|
||||
} else {
|
||||
last = nextRangeStart.plus({ days: 41 }).endOf('day');
|
||||
}
|
||||
}
|
||||
|
||||
return luxon.Interval.fromDateTimes(first, last);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentDate() {
|
||||
if (this.currentDate.locale != this.focusDate.locale) {
|
||||
this.focusDate = this.currentDate;
|
||||
this.$emit('update:range', this.range);
|
||||
} else {
|
||||
this.rangeOffset = this.currentDate.startOf('month').diff(this.focusDate.startOf('month'), 'months').months;
|
||||
if (this.rangeOffset) {
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.slidePages(this.rangeOffset).then(this.updatePage);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
prevPage() {
|
||||
this.rangeOffset = this.$refs.slider.target - 1;
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.prevPage().then(this.updatePage);
|
||||
},
|
||||
nextPage() {
|
||||
this.rangeOffset = this.$refs.slider.target + 1;
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.nextPage().then(this.updatePage);
|
||||
},
|
||||
updatePage(months) {
|
||||
const newFocusDate = this.focusDate.plus({ months });
|
||||
this.focusDate = newFocusDate;
|
||||
this.rangeOffset = 0;
|
||||
this.$emit('update:currentDate', this.focusDate);
|
||||
this.$emit('update:range', this.range);
|
||||
},
|
||||
viewAttrs(months) {
|
||||
const day = this.focusDate.plus({ months });
|
||||
return { day };
|
||||
},
|
||||
handleClickDefaults(evt) {
|
||||
switch (evt.detail.source) {
|
||||
case 'week':
|
||||
// default: Move to week if not in month
|
||||
let dayInWeek = luxon.DateTime.fromObject({
|
||||
localWeekNumber: evt.detail.value.number,
|
||||
localWeekYear: evt.detail.value.year
|
||||
}, {
|
||||
zone: this.currentDate.zoneName,
|
||||
locale: this.currentDate.locale
|
||||
});
|
||||
|
||||
if (!this.focusDate.hasSame(dayInWeek.startOf('week', { useLocaleWeeks: true }), 'month')) {
|
||||
this.$emit('update:currentDate', dayInWeek.startOf('week', { useLocaleWeeks: true }));
|
||||
} else if (!this.focusDate.hasSame(dayInWeek.endOf('week', { useLocaleWeeks: true }), 'month')) {
|
||||
this.$emit('update:currentDate', dayInWeek.endOf('week', { useLocaleWeeks: true }));
|
||||
}
|
||||
break;
|
||||
case 'day':
|
||||
// default: Set current-date
|
||||
this.$emit('update:currentDate', evt.detail.value);
|
||||
break;
|
||||
case 'event':
|
||||
// default: Request Modal
|
||||
this.$emit('requestModalOpen', { event: evt.detail.value });
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$emit('update:range', this.range);
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="fhc-calendar-mode-month flex-grow-1 position-relative"
|
||||
@cal-click-default.capture="handleClickDefaults"
|
||||
>
|
||||
<base-slider ref="slider" v-slot="slot">
|
||||
<month-view v-bind="viewAttrs(slot.offset)">
|
||||
<template v-slot="slot"><slot v-bind="slot" mode="month" /></template>
|
||||
</month-view>
|
||||
</base-slider>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import CalendarGrid from '../../Base/Grid.js';
|
||||
import LabelWeek from '../../Base/Label/Week.js';
|
||||
import LabelDow from '../../Base/Label/Dow.js';
|
||||
import LabelDay from '../../Base/Label/Day.js';
|
||||
|
||||
export default {
|
||||
name: "MonthView",
|
||||
components: {
|
||||
CalendarGrid,
|
||||
LabelWeek,
|
||||
LabelDow,
|
||||
LabelDay
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
// NOTE(chris): snap events to day
|
||||
events: Vue.computed(() => {
|
||||
//const events = [];
|
||||
const events = this.events.map(event => {
|
||||
const start = event.start.startOf('day');
|
||||
const end = event.end.plus({ days: 1 }).startOf('day');
|
||||
return {
|
||||
...event,
|
||||
start,
|
||||
end
|
||||
};
|
||||
});
|
||||
for (var w = 5; w > -1; w--) {
|
||||
for (var d = 6; d > -1; d--) {
|
||||
const startdate = this.axisMain[w].plus(this.axisParts[d]);
|
||||
events.unshift({
|
||||
start: startdate,
|
||||
end: startdate.plus({ days: 1 }),
|
||||
orig: 'header'
|
||||
});
|
||||
}
|
||||
}
|
||||
return events;
|
||||
})
|
||||
};
|
||||
},
|
||||
inject: {
|
||||
events: "events",
|
||||
timezone: "timezone"
|
||||
},
|
||||
props: {
|
||||
day: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
axisMain() {
|
||||
const start = this.day.startOf('month').startOf('week', { useLocaleWeeks: true });
|
||||
return Array.from({ length: 6 }, (e, i) => start.plus({ weeks: i }));
|
||||
},
|
||||
axisParts() {
|
||||
return Array.from({ length: 8 }, (e, i) => luxon.Duration.fromObject({ days: i }));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isToday(date) {
|
||||
return date.hasSame(luxon.DateTime.now().setZone(this.timezone), 'day');
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="fhc-calendar-mode-month-view h-100">
|
||||
<calendar-grid
|
||||
flip-axis
|
||||
:axis-main="axisMain"
|
||||
:axis-parts="axisParts"
|
||||
|
||||
snap-to-grid
|
||||
>
|
||||
<template #main-header="{ date }">
|
||||
<label-week v-bind="{ date }" />
|
||||
</template>
|
||||
<template #part-header="{ part }">
|
||||
<label-dow :date="axisMain[0].plus(part)" class="text-center" />
|
||||
</template>
|
||||
<template #event="slot">
|
||||
<label-day
|
||||
v-if="slot.event.orig == 'header'"
|
||||
:date="slot.event.start"
|
||||
:class="{ disabled: !day.hasSame(slot.event.start, 'month'), today: isToday(slot.event.start) }"
|
||||
/>
|
||||
<div v-else-if="slot.event.type == 'loading'" class="placeholder-glow opacity-50">
|
||||
<span class="placeholder w-100 fs-1" />
|
||||
</div>
|
||||
<slot v-else v-bind="slot" />
|
||||
</template>
|
||||
</calendar-grid>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import BaseSlider from '../Base/Slider.js';
|
||||
import WeekView from './Week/View.js';
|
||||
|
||||
export default {
|
||||
name: "ModeWeek",
|
||||
components: {
|
||||
BaseSlider,
|
||||
WeekView
|
||||
},
|
||||
props: {
|
||||
currentDate: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
"update:currentDate",
|
||||
"update:range",
|
||||
"click",
|
||||
"requestModalOpen"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
focusDate: this.currentDate,
|
||||
rangeOffset: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
range() {
|
||||
let first = this.focusDate.startOf('week', { useLocaleWeeks: true });
|
||||
let last = this.focusDate.endOf('week', { useLocaleWeeks: true });
|
||||
|
||||
if (this.rangeOffset != 0) {
|
||||
if (this.rangeOffset < 0) {
|
||||
first = first.plus({ weeks: this.rangeOffset });
|
||||
} else {
|
||||
last = last.plus({ weeks: this.rangeOffset });
|
||||
}
|
||||
}
|
||||
|
||||
return luxon.Interval.fromDateTimes(first, last);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentDate() {
|
||||
if (this.currentDate.locale != this.focusDate.locale) {
|
||||
this.focusDate = this.currentDate;
|
||||
this.$emit('update:range', this.range);
|
||||
} else {
|
||||
this.rangeOffset = this.currentDate.startOf('week', { useLocaleWeeks: true }).diff(this.focusDate.startOf('week', { useLocaleWeeks: true }), 'weeks').weeks;
|
||||
if (this.rangeOffset) {
|
||||
this.$refs.view.$refs.grid.disableAutoScroll();
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.slidePages(this.rangeOffset).then(this.updatePage);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
prevPage() {
|
||||
this.rangeOffset = this.$refs.slider.target - 1;
|
||||
this.$refs.view.$refs.grid.disableAutoScroll();
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.prevPage().then(this.updatePage);
|
||||
},
|
||||
nextPage() {
|
||||
this.rangeOffset = this.$refs.slider.target + 1;
|
||||
this.$refs.view.$refs.grid.disableAutoScroll();
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.nextPage().then(this.updatePage);
|
||||
},
|
||||
updatePage(weeks) {
|
||||
const newFocusDate = this.focusDate.plus({ weeks });
|
||||
this.focusDate = newFocusDate;
|
||||
this.rangeOffset = 0;
|
||||
this.$emit('update:currentDate', this.focusDate);
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.view.$refs.grid.enableAutoScroll();
|
||||
},
|
||||
viewAttrs(weeks) {
|
||||
const day = this.focusDate.plus({ weeks });
|
||||
return { ...this.$attrs, day };
|
||||
},
|
||||
handleClickDefaults(evt) {
|
||||
switch (evt.detail.source) {
|
||||
case 'day':
|
||||
// default: Set current-date
|
||||
this.$emit('update:currentDate', evt.detail.value);
|
||||
break;
|
||||
case 'event':
|
||||
// default: Request Modal
|
||||
this.$emit('requestModalOpen', { event: evt.detail.value });
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.view.$refs.grid.enableAutoScroll();
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="fhc-calendar-mode-week flex-grow-1 position-relative"
|
||||
@cal-click-default.capture="handleClickDefaults"
|
||||
>
|
||||
<base-slider ref="slider" v-slot="slot">
|
||||
<week-view ref="view" v-bind="viewAttrs(slot.offset)">
|
||||
<template v-slot="slot"><slot v-bind="slot" mode="week" /></template>
|
||||
</week-view>
|
||||
</base-slider>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import CalendarGrid from '../../Base/Grid.js';
|
||||
import LabelDay from '../../Base/Label/Day.js';
|
||||
import LabelDow from '../../Base/Label/Dow.js';
|
||||
import LabelTime from '../../Base/Label/Time.js';
|
||||
|
||||
export default {
|
||||
name: "WeekView",
|
||||
components: {
|
||||
CalendarGrid,
|
||||
LabelDay,
|
||||
LabelDow,
|
||||
LabelTime
|
||||
},
|
||||
inject: {
|
||||
timeGrid: "timeGrid",
|
||||
timezone: "timezone"
|
||||
},
|
||||
props: {
|
||||
day: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
collapseEmptyDays: Boolean
|
||||
},
|
||||
computed: {
|
||||
start() {
|
||||
return this.day.startOf('week', { useLocaleWeeks: true });
|
||||
},
|
||||
axisMain() {
|
||||
return Array.from({ length: 7 }, (e, i) => this.start.plus({ days: i }));
|
||||
},
|
||||
axisParts() {
|
||||
if (this.timeGrid) {
|
||||
// create {start, end} array
|
||||
return this.timeGrid.map(tu => {
|
||||
return {
|
||||
start: luxon.Duration.fromISOTime(tu.start),
|
||||
end: luxon.Duration.fromISOTime(tu.end)
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// create 07:00-23:00
|
||||
return Array.from({ length: 17 }, (e, i) => luxon.Duration.fromObject({ hours: i + 7 }));
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isToday(date) {
|
||||
return date.hasSame(luxon.DateTime.now().setZone(this.timezone), 'day');
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="fhc-calendar-mode-week-view h-100">
|
||||
<calendar-grid
|
||||
ref="grid"
|
||||
:axis-main="axisMain"
|
||||
:axis-parts="axisParts"
|
||||
:axis-main-collapsible="collapseEmptyDays"
|
||||
:snap-to-grid="!!timeGrid"
|
||||
all-day-events
|
||||
>
|
||||
<template #main-header="{ date }">
|
||||
<div :class="{ today: isToday(date) }">
|
||||
<label-dow
|
||||
v-bind="{ date }"
|
||||
@cal-click="evt => evt.detail.source = 'day'"
|
||||
/>
|
||||
<label-day
|
||||
v-bind="{ date }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #part-header="{ part }">
|
||||
<label-time v-bind="{ part }" />
|
||||
</template>
|
||||
<template #event="slot">
|
||||
<div v-if="slot.event.type == 'loading'" class="placeholder-glow h-100 opacity-50">
|
||||
<span class="placeholder w-100 h-100" />
|
||||
</div>
|
||||
<slot v-else v-bind="slot" />
|
||||
</template>
|
||||
</calendar-grid>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
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'}, this.$p.user_locale.value);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
paneChanged(dir) {
|
||||
if (this.syncOnNextChange) {
|
||||
this.syncOnNextChange = false;
|
||||
this.focusDate.set(this.date);
|
||||
} else {
|
||||
this.focusDate.moveMonthInDirection(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();
|
||||
this.$emit('change:offset', { y: 0, m: -1, d: 0 });
|
||||
},
|
||||
next() {
|
||||
this.$refs.pane.next();
|
||||
this.$emit('change:offset', { y: 0, m: 1, d: 0 });
|
||||
},
|
||||
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" @updateMode="$emit('updateMode', $event)" @click="$emit('updateMode', 'months')" >
|
||||
<template #calendarDownloads>
|
||||
<slot name="calendarDownloads"></slot>
|
||||
</template>
|
||||
</calendar-header>
|
||||
<calendar-pane ref="pane" v-slot="slot" @slid="paneChanged">
|
||||
<calendar-month-page :year="focusDate.y" :month="focusDate.m+slot.offset" @updateMode="$emit('updateMode', $event)" @page:back="prev" @page:forward="next" @input="selectDay" >
|
||||
<template #monthPage="{event,day}">
|
||||
<slot name="monthPage" :event="event" :day="day" ></slot>
|
||||
</template>
|
||||
</calendar-month-page>
|
||||
</calendar-pane>
|
||||
</div>`
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
import CalendarDate from '../../../composables/CalendarDate.js';
|
||||
|
||||
export default {
|
||||
name: 'MonthPage',
|
||||
data(){
|
||||
return{
|
||||
highlightedWeek: null,
|
||||
highlightedDay: null,
|
||||
}
|
||||
},
|
||||
inject: [
|
||||
'today',
|
||||
'todayDate',
|
||||
'date',
|
||||
'focusDate',
|
||||
'size',
|
||||
'events',
|
||||
'showWeeks',
|
||||
'noWeekView',
|
||||
'selectedEvent',
|
||||
'setSelectedEvent'
|
||||
],
|
||||
props: {
|
||||
year: Number,
|
||||
month: Number
|
||||
},
|
||||
emits: [
|
||||
'updateMode',
|
||||
'page:back',
|
||||
'page:forward',
|
||||
'input'
|
||||
],
|
||||
computed: {
|
||||
dayText(){
|
||||
if (!this.size || !this.weeks[0]?.days) return {};
|
||||
let dayTextMap ={};
|
||||
this.weeks[0].days.forEach((day)=>{
|
||||
dayTextMap[day] = day.toLocaleString(this.$p.user_locale.value, { weekday: this.size < 1 ? 'narrow' : (this.size < 3 ? 'short' : 'long') });
|
||||
});
|
||||
return dayTextMap;
|
||||
},
|
||||
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[5]);
|
||||
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: {
|
||||
getDayClass(week, day) {
|
||||
let classstring = 'fhc-calendar-month-page-day text-decoration-none overflow-hidden'
|
||||
const isHighlightedWeek = this.isHighlightedWeek(week)
|
||||
const isHighlightedDay = this.isHighlightedDay(day)
|
||||
const isThisDate = this.date.compare(day)
|
||||
const isNotThisMonth = day.getMonth() != this.month
|
||||
const isInThePast = day.getTime() < this.today // this.date is just the focusDate but not the initial Date
|
||||
|
||||
if(isHighlightedWeek) classstring += ' fhc-highlight-week'
|
||||
if(isHighlightedDay) classstring += ' fhc-highlight-day'
|
||||
|
||||
if(isNotThisMonth) classstring += ' opacity-25'
|
||||
if(isInThePast) classstring += ' fhc-calendar-past'
|
||||
return classstring
|
||||
},
|
||||
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('updateMode', 'week');
|
||||
}
|
||||
},
|
||||
highlight(week, day){
|
||||
this.highlightedWeek = week.no;
|
||||
this.highlightedDay = day;
|
||||
},
|
||||
isHighlightedDay(day) {
|
||||
return day == this.highlightedDay
|
||||
},
|
||||
isHighlightedWeek(week) {
|
||||
return week.no == this.highlightedWeek
|
||||
},
|
||||
clickEvent(day,week) {
|
||||
if(!this.noWeekView)
|
||||
{
|
||||
this.focusDate.set(day);
|
||||
this.$emit('updateMode', 'day');
|
||||
}
|
||||
this.selectDay(day);
|
||||
},
|
||||
getNumberStyle(day) {
|
||||
|
||||
const styleObj = {}
|
||||
styleObj.display = 'inline-block';
|
||||
styleObj.height = '32px';
|
||||
styleObj['line-height'] = '32px';
|
||||
styleObj['text-align'] = 'center';
|
||||
styleObj['font-weight'] = 'bold';
|
||||
styleObj['font-size'] = '14px';
|
||||
|
||||
if(day.getDate() === this.todayDate.getDate()
|
||||
&& day.getMonth() === this.todayDate.getMonth()
|
||||
&& day.getFullYear() === this.todayDate.getFullYear()) {
|
||||
styleObj['background-color'] = '#00649c'; // fh blau
|
||||
styleObj.color = 'white';
|
||||
}
|
||||
|
||||
return styleObj
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const container = document.getElementById("calendarContainer")
|
||||
if(container) container.style['overflow-y'] = 'auto'
|
||||
|
||||
},
|
||||
template: /*html*/`
|
||||
<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">
|
||||
{{dayText[day]}}
|
||||
</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="#"
|
||||
@click="clickEvent(day,week)"
|
||||
@mouseover="highlight(week,day)"
|
||||
@mouseleave="highlightedWeek = null; highlightedDay = null"
|
||||
v-for="day in week.days"
|
||||
:key="day"
|
||||
:class="getDayClass(week, day)"
|
||||
>
|
||||
<span @click="clickEvent(day,week)" class="no" :style="getNumberStyle(day)">{{day.getDate()}}</span>
|
||||
<span v-if="events[day.toDateString()] && events[day.toDateString()].length" class="events">
|
||||
<div v-for="event in events[day.toDateString()]" :key="event.id"
|
||||
:style="{'background-color': event.color}" class="fhc-entry" :selected="event == selectedEvent" v-contrast >
|
||||
<slot name="monthPage" :event="event" :day="day" >
|
||||
<p>this is a placeholder which means that no template was passed to the Calendar Page slot</p>
|
||||
</slot>
|
||||
</div>
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
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(this.$p.user_locale.value, {month: this.size < 2 ? 'short' : 'long'}));
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-months">
|
||||
<calendar-header :title="title" @prev="focusDate.y--" @next="focusDate.y++" @click="$emit('updateMode', 'years')" @updateMode="$emit('updateMode', $event)" />
|
||||
<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('updateMode', 'month')" class="btn btn-outline-secondary" :class="{'border-0': key != focusDate.m}">
|
||||
{{month}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
export default {
|
||||
name: 'Pane',
|
||||
emits: [
|
||||
'slid'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
carousel: null,
|
||||
queue: 0,
|
||||
offset: 0,
|
||||
slideAnimation:false,
|
||||
scrollTop:null,
|
||||
clientHeight:null,
|
||||
carouselItems:null,
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
isSliding: Vue.computed(() => this.slideAnimation),
|
||||
calendarScrollTop: Vue.computed(() =>this.scrollTop),
|
||||
calendarClientHeight: Vue.computed(() => this.clientHeight),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
offsets() {
|
||||
return [...Array(3).keys()].map(i => (3+i-this.offset)%3-1);
|
||||
},
|
||||
activeCarouselItemIndex() {
|
||||
if (Array.isArray(this.carouselItems) && this.carouselItems.length > 0) {
|
||||
for(let index=0; index < this.carouselItems.length; index++){
|
||||
if (this.carouselItems[index] == true){
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
},
|
||||
methods: {
|
||||
scrollCalendar(event){
|
||||
this.scrollTop = this.$refs.calendarContainer.scrollTop;
|
||||
this.clientHeight = this.$refs.calendarContainer.clientHeight;
|
||||
},
|
||||
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();
|
||||
}
|
||||
this.carouselItems = this.$refs.carouselItems.map((item) => { return item.classList.contains('active') });
|
||||
this.slideAnimation = false;
|
||||
},
|
||||
slide(evt) {
|
||||
this.slideAnimation = true;
|
||||
}
|
||||
},
|
||||
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
|
||||
});
|
||||
}
|
||||
this.carouselItems = this.$refs.carouselItems.map((item)=>{return item.classList.contains('active')});
|
||||
this.scrollTop = this.$refs.calendarContainer.scrollTop;
|
||||
this.clientHeight = this.$refs.calendarContainer.clientHeight;
|
||||
},
|
||||
template: `
|
||||
<div ref="carousel" class="calendar-pane carousel slide" @[\`slide.bs.carousel\`]="slide" @[\`slid.bs.carousel\`]="slid" :data-queue="queue">
|
||||
<div id="calendarContainer" @scroll="scrollCalendar" ref="calendarContainer" class="carousel-inner " style="height:var(--fhc-calendar-pane-height);">
|
||||
<div ref="carouselItems" v-for="i in [...Array(3).keys()]" :key="i" class="carousel-item" style="height:var(--fhc-calendar-pane-height);">
|
||||
<slot :active="i == activeCarouselItemIndex" :index="i" :offset="offsets[i]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
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.wYear + ' 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();
|
||||
this.$emit('change:offset', { y: 0, m: 0, d: -7 });
|
||||
},
|
||||
next() {
|
||||
this.$refs.pane.next();
|
||||
this.$emit('change:offset', { y: 0, m: 0, d: 7 });
|
||||
},
|
||||
selectEvent(event) {
|
||||
this.$emit('input', ['select:event',event]);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.emitRangeChanged();
|
||||
},
|
||||
template: /*html*/`
|
||||
<div class="fhc-calendar-week">
|
||||
<calendar-header :title="title" @prev="prev" @next="next" @updateMode="$emit('updateMode', $event)" @click="$emit('updateMode', 'weeks')">
|
||||
<template #calendarDownloads>
|
||||
<slot name="calendarDownloads"></slot>
|
||||
</template>
|
||||
</calendar-header>
|
||||
<calendar-pane ref="pane" v-slot="slot" @slid="paneChanged">
|
||||
<calendar-week-page :year="focusDate.wYear" :week="focusDate.w+slot.offset" @updateMode="$emit('updateMode', $event)" @page:back="prev" @page:forward="next" @input="selectEvent" >
|
||||
<template #weekPage="{event,day}">
|
||||
<slot name="weekPage" :event="event" :day="day" ></slot>
|
||||
</template>
|
||||
</calendar-week-page>
|
||||
</calendar-pane>
|
||||
</div>`
|
||||
}
|
||||
@@ -1,368 +0,0 @@
|
||||
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 {
|
||||
name: 'WeekPage',
|
||||
data(){
|
||||
return{
|
||||
hourPosition:null,
|
||||
hourPositionTime:null,
|
||||
resizeObserver: null,
|
||||
width: 0
|
||||
}
|
||||
},
|
||||
inject: [
|
||||
'today',
|
||||
'todayDate',
|
||||
'date',
|
||||
'focusDate',
|
||||
'size',
|
||||
'events',
|
||||
'noMonthView',
|
||||
'isSliding',
|
||||
'selectedEvent',
|
||||
'setSelectedEvent',
|
||||
'rowMinHeight'
|
||||
],
|
||||
props: {
|
||||
year: Number,
|
||||
week: Number
|
||||
},
|
||||
emits: [
|
||||
'updateMode',
|
||||
'page:back',
|
||||
'page:forward',
|
||||
'input',
|
||||
],
|
||||
computed: {
|
||||
getGridStyle() {
|
||||
return {
|
||||
'min-height': this.rowMinHeight,
|
||||
// this.size is the magic number anyway which directs font-size,
|
||||
// which in turn influences a lot of layout
|
||||
width: '42px'
|
||||
}
|
||||
},
|
||||
laneWidth() {
|
||||
return (this.width - 42) / this.days.length
|
||||
},
|
||||
curTime() {
|
||||
const now = new Date();
|
||||
return String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0');
|
||||
},
|
||||
pageHeaderStyle(){
|
||||
return {
|
||||
'z-index': 4,
|
||||
'grid-template-columns': 'repeat(' + this.days.length + ', 1fr)',
|
||||
'grid-template-rows': 1,
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
}
|
||||
},
|
||||
overlayStyle() {
|
||||
return {
|
||||
'background-color': '#F5E9D7',
|
||||
'position': 'absolute',
|
||||
'pointer-events': 'none',
|
||||
'z-index': 2,
|
||||
height: this.getDayTimePercent + '%',
|
||||
width: this.laneWidth + 'px',
|
||||
opacity: 0.5
|
||||
}
|
||||
},
|
||||
indicatorStyle() {
|
||||
return {
|
||||
'pointer-events': 'none',
|
||||
'padding-left': '3.5rem',
|
||||
'margin-top': '-1px',
|
||||
'z-index': 2,
|
||||
'border-color': '#00649C!important',
|
||||
top: this.hourPosition + 'px',
|
||||
left: 0,
|
||||
right: 0,
|
||||
}
|
||||
},
|
||||
hours(){
|
||||
// returns an array with elements starting at 7 and ending at 24
|
||||
return [...Array(24).keys()].filter(hour => hour >= 7 && hour <= 24);
|
||||
},
|
||||
dayText() {
|
||||
if (!this.size || !this.days) return {};
|
||||
let dayTextMap ={};
|
||||
this.days.forEach((day)=>{
|
||||
dayTextMap[day] = {
|
||||
heading: day.toLocaleString(this.$p.user_locale.value, { dateStyle: 'short' }),
|
||||
tag: day.toLocaleString(this.$p.user_locale.value, { weekday: this.size < 2 ? 'narrow' : (this.size < 3 ? 'short' : 'long') }),
|
||||
datum: day.toLocaleString(this.$p.user_locale.value, [{ day: 'numeric', month: 'numeric' }, { day: 'numeric', month: 'numeric' }, { day: 'numeric', month: 'numeric' }, { dateStyle: 'short' }][this.size]),
|
||||
};
|
||||
});
|
||||
return dayTextMap;
|
||||
},
|
||||
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() {
|
||||
// return early if the calendar pane is sliding
|
||||
if (this.isSliding) return {};
|
||||
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, isPast: false};
|
||||
d.isPast = nextDay.getTime() < this.today
|
||||
d.isToday = nextDay.getFullYear() === this.todayDate.getFullYear() && nextDay.getMonth() === this.todayDate.getMonth() && nextDay.getDate() === this.todayDate.getDate()
|
||||
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];
|
||||
},
|
||||
lookingAtToday() {
|
||||
return this.days.some(d =>
|
||||
d.getFullYear() === this.todayDate.getFullYear() &&
|
||||
d.getMonth() === this.todayDate.getMonth() &&
|
||||
d.getDate() === this.todayDate.getDate()
|
||||
)
|
||||
},
|
||||
curIndicatorStyle() {
|
||||
|
||||
return {
|
||||
'pointer-events': 'none',
|
||||
'padding-left': '1rem',
|
||||
'margin-top': '-1px',
|
||||
'z-index': 2,
|
||||
'border-color': '#00649C!important',
|
||||
top: this.getDayTimePercent + '%',
|
||||
width: this.laneWidth + 'px'
|
||||
}
|
||||
},
|
||||
getDayTimePercent() {
|
||||
const now = new Date(Date.now())
|
||||
const currentMinutes = now.getMinutes() + now.getHours() * 60
|
||||
let timePercentage = ((currentMinutes - (this.hours[0] * 60)) / (this.hours.length * 60)) * 100;
|
||||
|
||||
return timePercentage
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hourGridIdentifier(hour) {
|
||||
// this is the id attribute that is responsible to scroll the calender to the first event
|
||||
return 'scroll' + hour + this.focusDate.d + this.week;
|
||||
},
|
||||
hourGridStyle(hour) {
|
||||
return {
|
||||
'pointer-events': 'none',
|
||||
top: this.getAbsolutePositionForHour(hour),
|
||||
left: 0,
|
||||
right: 0,
|
||||
'z-index': 0
|
||||
}
|
||||
},
|
||||
dayGridStyle(day) {
|
||||
const styleObj = {
|
||||
'grid-template-columns': 'repeat(' + day.lanes + ', 1fr)',
|
||||
'grid-template-rows': 'repeat(' + (this.hours.length * 60 / this.smallestTimeFrame) + ', 1fr)',
|
||||
}
|
||||
|
||||
if(day.isPast) {
|
||||
styleObj['background-color'] = '#F5E9D7'
|
||||
styleObj['border-color'] = '#E8E8E8';
|
||||
styleObj.opacity = 0.5;
|
||||
} else if (day.isToday) {
|
||||
|
||||
// styleObj['backgroundImage'] = 'linear-gradient(to bottom, #F5E9D7 '+this.getDayTimePercent+'%, #FFFFFF '+this.getDayTimePercent+'%)'
|
||||
// styleObj['border-color'] = '#E8E8E8';
|
||||
// styleObj.opacity = 0.5;
|
||||
}
|
||||
|
||||
return styleObj
|
||||
},
|
||||
eventGridStyle(day, event) {
|
||||
return {
|
||||
'z-index': 1,
|
||||
'grid-column-start': 1 + (event.lane - 1) * day.lanes / event.maxLane,
|
||||
'grid-column-end': 1 + event.lane * day.lanes / event.maxLane,
|
||||
'grid-row-start': this.dateToMinutesOfDay(event.start),
|
||||
'grid-row-end': this.dateToMinutesOfDay(event.end),
|
||||
'background-color': event.orig.color,
|
||||
'max-height': '75px'
|
||||
}
|
||||
},
|
||||
calcHourPosition(event) {
|
||||
let height = this.$refs.eventcontainer.getBoundingClientRect().height;
|
||||
let top = this.$refs.eventcontainer.getBoundingClientRect().top;
|
||||
let position = event.clientY - top;
|
||||
// position percentage of total height
|
||||
let timePercentage = (position / height) * 100;
|
||||
// minute percentage of total minutes
|
||||
let result = (this.hours.length * 60) * (timePercentage / 100);
|
||||
// calculate time in float
|
||||
let currentMinutes = ((result + (this.hours[0] * 60)) / 60);
|
||||
// get hour part of time
|
||||
let currentHour = Math.floor(currentMinutes);
|
||||
// get float part of time
|
||||
let minutePercentage = currentMinutes % currentHour;
|
||||
// calculate minutes from float part of time
|
||||
let minute = Math.round(60 * minutePercentage);
|
||||
// convert minutes to 5 minute interval
|
||||
if (minute % 5 != 0) {
|
||||
minute = Math.round(minute / 5) * 5;
|
||||
}
|
||||
// in case the rounding made the minutes 60, increase the hour and reset the minutes
|
||||
if (minute == 60) {
|
||||
currentHour++;
|
||||
minute = 0;
|
||||
}
|
||||
|
||||
// ## after rounding the time to the nearest 5 Minute interval, we have to convert the time back to the relative position
|
||||
// convert current time in minutes
|
||||
currentMinutes = currentHour * 60 + minute;
|
||||
// calculate the minutes percentage of the total minutes
|
||||
timePercentage = ((currentMinutes - (this.hours[0] * 60)) / (this.hours.length * 60)) * 100;
|
||||
// calculate the relative position of the time percentage
|
||||
position = height * (timePercentage / 100);
|
||||
this.hourPosition = position;
|
||||
|
||||
// add padding to minutes that consist of only one digit
|
||||
minute.toString().length == 1 ? minute = "0" + minute : minute;
|
||||
this.hourPositionTime = currentHour + ":" + minute;
|
||||
},
|
||||
getAbsolutePositionForHour(hour){
|
||||
// used for the absolute positioning of the gutters of hours
|
||||
return (100 / this.hours.length) * (hour - (24-this.hours.length)) + '%';
|
||||
},
|
||||
changeToMonth(day) {
|
||||
if (!this.noMonthView) {
|
||||
this.date.set(day);
|
||||
this.focusDate.set(day);
|
||||
this.$emit('updateMode', 'month');
|
||||
}
|
||||
},
|
||||
changeToDay(day) {
|
||||
this.date.set(day);
|
||||
this.focusDate.set(day);
|
||||
this.$emit('updateMode', 'day');
|
||||
},
|
||||
dateToMinutesOfDay(day) {
|
||||
// subtract 7 from the total hours because the hours range from 7 to 24
|
||||
return Math.floor(((day.getHours()-7) * 60 + day.getMinutes()) / this.smallestTimeFrame) + 1;
|
||||
},
|
||||
weekPageClick(event, day) {
|
||||
this.setSelectedEvent(event);
|
||||
this.focusDate.set(new CalendarDate(new Date(event.datum)));
|
||||
this.$emit('input', event)
|
||||
},
|
||||
initResizeObserver() {
|
||||
const events = this.$refs['eventsRef'+this.week];
|
||||
if (!events) return;
|
||||
|
||||
this.resizeObserver = new ResizeObserver((entries) => {
|
||||
for (let entry of entries) {
|
||||
const { width, height } = entry.contentRect;
|
||||
if(width > 0) this.width = width
|
||||
}
|
||||
});
|
||||
|
||||
this.resizeObserver.observe(events);
|
||||
},
|
||||
destroyResizeObserver() {
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
this.resizeObserver = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
||||
setTimeout(() => this.$refs.eventcontainer.scrollTop = this.$refs.eventcontainer.scrollHeight / 3 + 1, 0);
|
||||
|
||||
const container = document.getElementById("calendarContainer")
|
||||
if(container) {
|
||||
container.style['overflow-y'] = 'scroll'
|
||||
container.style['overflow-x'] = 'auto'
|
||||
}
|
||||
|
||||
this.initResizeObserver();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.destroyResizeObserver();
|
||||
},
|
||||
template: /*html*/`
|
||||
<div ref="page" class="fhc-calendar-week-page" style="min-width: 700px;">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="fhc-calendar-week-page-header d-grid border-2 border-bottom text-center" :style="pageHeaderStyle" >
|
||||
<div type="button" v-for="day in days" :key="day" class="flex-grow-1" :title="dayText[day]?.heading" @click.prevent="changeToDay(day)">
|
||||
<div class="fw-bold">{{dayText[day]?.tag}}</div>
|
||||
<a href="#" class="small text-secondary text-decoration-none" >{{dayText[day]?.datum}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="eventcontainer" class="position-relative flex-grow-1" @mousemove="calcHourPosition" @mouseleave="hourPosition = null" >
|
||||
<div :id="hourGridIdentifier(hour)" v-for="hour in hours" :key="hour" class="position-absolute box-shadow-border-top" :style="hourGridStyle(hour)"></div>
|
||||
<Transition>
|
||||
<div v-if="hourPosition" class="position-absolute border-top small" :style="indicatorStyle">
|
||||
<span class="border border-top-0 px-2 bg-white">{{hourPositionTime}}</span>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<div class="events" :ref="'eventsRef'+week">
|
||||
<div class="hours">
|
||||
<div v-for="hour in hours" :style="getGridStyle" :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="dayGridStyle(day)">
|
||||
<Transition>
|
||||
<div v-if="day.isToday" class="position-absolute border-top small" :style="curIndicatorStyle">
|
||||
<span class="border border-top-0 px-2 bg-white">{{curTime}}</span>
|
||||
</div>
|
||||
</Transition>
|
||||
<div v-if="day.isToday" :style="overlayStyle"></div>
|
||||
<div v-for="event in day.events" :key="event" @click.prevent="weekPageClick(event.orig, day)"
|
||||
:selected="event.orig == selectedEvent"
|
||||
:style="eventGridStyle(day,event)"
|
||||
class="mx-2 small rounded overflow-hidden fhc-entry "
|
||||
v-contrast >
|
||||
<slot name="weekPage" :event="event" :day="day">
|
||||
<p>this is a placeholder which means that no template was passed to the Calendar Page slot</p>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import CalendarAbstract from './Abstract.js';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
CalendarAbstract
|
||||
],
|
||||
inject: [
|
||||
'size',
|
||||
'focusDate'
|
||||
],
|
||||
computed: {
|
||||
weeks(){
|
||||
return [...Array(this.focusDate.numWeeks).keys()].map(i => i + 1);
|
||||
},
|
||||
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('updateMode', 'week');
|
||||
},
|
||||
prev(){
|
||||
this.focusDate.y--;
|
||||
this.focusDate._clean();
|
||||
},
|
||||
next() {
|
||||
this.focusDate.y++;
|
||||
this.focusDate._clean();
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-weeks h-100">
|
||||
<calendar-header :title="title" @prev="prev" @next="next" @click="$emit('updateMode', 'years')" @updateMode="$emit('updateMode', $event)" />
|
||||
<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>`
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user