mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender.git
synced 2025-08-02 17:59:14 +02:00
21 add reminder booleans, map uuid to module
This commit is contained in:
@@ -27,4 +27,5 @@ type FeedCollection struct {
|
|||||||
Name string `db:"Name" json:"name"`
|
Name string `db:"Name" json:"name"`
|
||||||
Course string `db:"course" json:"course"`
|
Course string `db:"course" json:"course"`
|
||||||
UserDefinedName string `db:"userDefinedName" json:"userDefinedName"`
|
UserDefinedName string `db:"userDefinedName" json:"userDefinedName"`
|
||||||
|
Reminder bool `db:"reminder" json:"reminder"`
|
||||||
}
|
}
|
||||||
|
@@ -118,18 +118,23 @@ func buildIcalQueryForModules(modules []model.FeedCollection) dbx.Expression {
|
|||||||
|
|
||||||
// GetPlanForModules returns all events for the given modules with the given course
|
// GetPlanForModules returns all events for the given modules with the given course
|
||||||
// used for the ical feed
|
// used for the ical feed
|
||||||
func GetPlanForModules(app *pocketbase.PocketBase, modules []model.FeedCollection) model.Events {
|
func GetPlanForModules(app *pocketbase.PocketBase, modules map[string]model.FeedCollection) model.Events {
|
||||||
|
|
||||||
var events model.Events
|
var events model.Events
|
||||||
|
|
||||||
|
modulesArray := make([]model.FeedCollection, 0, len(modules))
|
||||||
|
for _, value := range modules {
|
||||||
|
modulesArray = append(modulesArray, value)
|
||||||
|
}
|
||||||
|
|
||||||
// iterate over modules in 100 batch sizes
|
// iterate over modules in 100 batch sizes
|
||||||
for i := 0; i < len(modules); i += 100 {
|
for i := 0; i < len(modules); i += 100 {
|
||||||
var moduleBatch []model.FeedCollection
|
var moduleBatch []model.FeedCollection
|
||||||
|
|
||||||
if i+100 > len(modules) {
|
if i+100 > len(modules) {
|
||||||
moduleBatch = modules[i:]
|
moduleBatch = modulesArray[i:]
|
||||||
} else {
|
} else {
|
||||||
moduleBatch = modules[i : i+100]
|
moduleBatch = modulesArray[i : i+100]
|
||||||
}
|
}
|
||||||
|
|
||||||
var selectedModulesQuery = buildIcalQueryForModules(moduleBatch)
|
var selectedModulesQuery = buildIcalQueryForModules(moduleBatch)
|
||||||
|
@@ -3,14 +3,15 @@ package ical
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/jordic/goics"
|
|
||||||
"github.com/labstack/echo/v5"
|
|
||||||
"github.com/pocketbase/pocketbase"
|
|
||||||
"github.com/pocketbase/pocketbase/apis"
|
|
||||||
"htwkalender/model"
|
"htwkalender/model"
|
||||||
"htwkalender/service/db"
|
"htwkalender/service/db"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jordic/goics"
|
||||||
|
"github.com/labstack/echo/v5"
|
||||||
|
"github.com/pocketbase/pocketbase"
|
||||||
|
"github.com/pocketbase/pocketbase/apis"
|
||||||
)
|
)
|
||||||
|
|
||||||
const expirationTime = 5 * time.Minute
|
const expirationTime = 5 * time.Minute
|
||||||
@@ -23,8 +24,13 @@ func Feed(c echo.Context, app *pocketbase.PocketBase, token string) error {
|
|||||||
return c.JSON(http.StatusNotFound, err)
|
return c.JSON(http.StatusNotFound, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var modules []model.FeedCollection
|
modules := make(map[string]model.FeedCollection)
|
||||||
_ = json.Unmarshal([]byte(feed.Modules), &modules)
|
var modulesArray []model.FeedCollection
|
||||||
|
_ = json.Unmarshal([]byte(feed.Modules), &modulesArray)
|
||||||
|
|
||||||
|
for _, module := range modulesArray {
|
||||||
|
modules[module.UUID] = module
|
||||||
|
}
|
||||||
|
|
||||||
newFeed, err := createFeedForToken(app, modules)
|
newFeed, err := createFeedForToken(app, modules)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -41,7 +47,7 @@ func Feed(c echo.Context, app *pocketbase.PocketBase, token string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createFeedForToken(app *pocketbase.PocketBase, modules []model.FeedCollection) (*model.FeedModel, error) {
|
func createFeedForToken(app *pocketbase.PocketBase, modules map[string]model.FeedCollection) (*model.FeedModel, error) {
|
||||||
res := db.GetPlanForModules(app, modules)
|
res := db.GetPlanForModules(app, modules)
|
||||||
b := bytes.Buffer{}
|
b := bytes.Buffer{}
|
||||||
goics.NewICalEncode(&b).Encode(IcalModel{Events: res, Mapping: modules})
|
goics.NewICalEncode(&b).Encode(IcalModel{Events: res, Mapping: modules})
|
||||||
|
@@ -12,7 +12,7 @@ import (
|
|||||||
// IcalModel local type for EmitICal function
|
// IcalModel local type for EmitICal function
|
||||||
type IcalModel struct {
|
type IcalModel struct {
|
||||||
Events model.Events
|
Events model.Events
|
||||||
Mapping []model.FeedCollection
|
Mapping map[string]model.FeedCollection
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmitICal implements the interface for goics
|
// EmitICal implements the interface for goics
|
||||||
@@ -27,13 +27,22 @@ func (icalModel IcalModel) EmitICal() goics.Componenter {
|
|||||||
c.AddProperty("X-WR-TIMEZONE", "Europe/Berlin")
|
c.AddProperty("X-WR-TIMEZONE", "Europe/Berlin")
|
||||||
c.AddProperty("X-LIC-LOCATION", "Europe/Berlin")
|
c.AddProperty("X-LIC-LOCATION", "Europe/Berlin")
|
||||||
for _, event := range icalModel.Events {
|
for _, event := range icalModel.Events {
|
||||||
|
mapEntry, mappingFound := icalModel.Mapping[event.UUID]
|
||||||
|
|
||||||
s := goics.NewComponent()
|
s := goics.NewComponent()
|
||||||
s.SetType("VEVENT")
|
s.SetType("VEVENT")
|
||||||
k, v := goics.FormatDateTime("DTEND;TZID=Europe/Berlin", event.End.Time().Local().In(europeTime))
|
k, v := goics.FormatDateTime("DTEND;TZID=Europe/Berlin", event.End.Time().Local().In(europeTime))
|
||||||
s.AddProperty(k, v)
|
s.AddProperty(k, v)
|
||||||
k, v = goics.FormatDateTime("DTSTART;TZID=Europe/Berlin", event.Start.Time().Local().In(europeTime))
|
k, v = goics.FormatDateTime("DTSTART;TZID=Europe/Berlin", event.Start.Time().Local().In(europeTime))
|
||||||
s.AddProperty(k, v)
|
s.AddProperty(k, v)
|
||||||
s.AddProperty("SUMMARY", replaceNameIfUserDefined(&event, icalModel.Mapping))
|
|
||||||
|
if mappingFound {
|
||||||
|
s.AddProperty("SUMMARY", replaceNameIfUserDefined(&event, mapEntry))
|
||||||
|
addAlarmIfSpecified(s, event, mapEntry)
|
||||||
|
} else {
|
||||||
|
s.AddProperty("SUMMARY", event.Name)
|
||||||
|
}
|
||||||
|
|
||||||
s.AddProperty("DESCRIPTION", generateDescription(event))
|
s.AddProperty("DESCRIPTION", generateDescription(event))
|
||||||
s.AddProperty("LOCATION", event.Rooms)
|
s.AddProperty("LOCATION", event.Rooms)
|
||||||
c.AddComponent(s)
|
c.AddComponent(s)
|
||||||
@@ -41,12 +50,22 @@ func (icalModel IcalModel) EmitICal() goics.Componenter {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func replaceNameIfUserDefined(event *model.Event, mapping []model.FeedCollection) string {
|
func addAlarmIfSpecified(s *goics.Component, event model.Event, mapping model.FeedCollection) {
|
||||||
for _, mapEntry := range mapping {
|
if mapping.Reminder {
|
||||||
if mapEntry.Name == event.Name && !functions.OnlyWhitespace(mapEntry.UserDefinedName) {
|
a := goics.NewComponent()
|
||||||
return names.ReplaceTemplateSubStrings(mapEntry.UserDefinedName, *event)
|
a.SetType("VALARM")
|
||||||
}
|
a.AddProperty("TRIGGER", "-PT15M")
|
||||||
|
a.AddProperty("ACTION", "DISPLAY")
|
||||||
|
a.AddProperty("DESCRIPTION", "Next course: "+replaceNameIfUserDefined(&event, mapping)+" in "+event.Rooms)
|
||||||
|
s.AddComponent(a)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func replaceNameIfUserDefined(event *model.Event, mapping model.FeedCollection) string {
|
||||||
|
if !functions.OnlyWhitespace(mapping.UserDefinedName) {
|
||||||
|
return names.ReplaceTemplateSubStrings(mapping.UserDefinedName, *event)
|
||||||
|
}
|
||||||
|
|
||||||
return event.Name
|
return event.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -17,6 +17,7 @@ const tableData = ref(
|
|||||||
const columns = ref([
|
const columns = ref([
|
||||||
{ field: "Course", header: "Course" },
|
{ field: "Course", header: "Course" },
|
||||||
{ field: "Module", header: "Module" },
|
{ field: "Module", header: "Module" },
|
||||||
|
{ field: "Reminder", header: "Reminder"}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const helpVisible: Ref<boolean> = ref(false);
|
const helpVisible: Ref<boolean> = ref(false);
|
||||||
@@ -36,7 +37,7 @@ async function finalStep() {
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-column">
|
<div class="flex flex-column">
|
||||||
<div class="flex align-items-center justify-content-center h-4rem m-2">
|
<div class="flex align-items-center justify-content-center h-4rem m-2">
|
||||||
<h3>Rename your selected Modules to your liking.</h3>
|
<h3>Configure your selected Modules to your liking.</h3>
|
||||||
<i class="shadow-3 hover:shadow-8 pi pi-info-circle m-2" style="font-size: 1.5rem; color: orange" @click="helpVisible = true"></i>
|
<i class="shadow-3 hover:shadow-8 pi pi-info-circle m-2" style="font-size: 1.5rem; color: orange" @click="helpVisible = true"></i>
|
||||||
<Dialog
|
<Dialog
|
||||||
v-model:visible="helpVisible"
|
v-model:visible="helpVisible"
|
||||||
@@ -65,30 +66,68 @@ async function finalStep() {
|
|||||||
table-class="editable-cells-table"
|
table-class="editable-cells-table"
|
||||||
responsive-layout="scroll"
|
responsive-layout="scroll"
|
||||||
>
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="flex align-items-center justify-content-end">
|
||||||
|
Enable all notifications:
|
||||||
|
<InputSwitch
|
||||||
|
class="mx-4"
|
||||||
|
:model-value="tableData.reduce((acc, curr) => acc && curr.Module.reminder, true)"
|
||||||
|
@update:model-value="tableData.forEach((module) => module.Module.reminder = $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<Column
|
<Column
|
||||||
v-for="col of columns"
|
v-for="col of columns"
|
||||||
:key="col.field"
|
:key="col.field"
|
||||||
:field="col.field"
|
:field="col.field"
|
||||||
:header="col.header"
|
:header="col.header"
|
||||||
|
:class="col.field === 'Reminder' ? 'text-center' : ''"
|
||||||
>
|
>
|
||||||
|
<!-- Text Body -->
|
||||||
<template #body="{ data, field }">
|
<template #body="{ data, field }">
|
||||||
<div>
|
<template v-if="field === 'Module'">
|
||||||
{{
|
{{ data[field].userDefinedName }}
|
||||||
field === "Module" ? data[field].userDefinedName : data[field]
|
</template>
|
||||||
}}
|
<template v-else-if="field === 'Reminder'" class="align-content-center">
|
||||||
</div>
|
<Button
|
||||||
</template>
|
:icon="data.Module.reminder ? 'pi pi-bell' : 'pi pi-times'"
|
||||||
<template #editor="{ data, field }">
|
:severity="data.Module.reminder ? 'warning' : 'secondary'"
|
||||||
<template v-if="field !== 'Module'">
|
rounded
|
||||||
<div>{{ data[field] }}</div>
|
outlined
|
||||||
|
class="small-button"
|
||||||
|
@click = "data.Module.reminder = !data.Module.reminder"
|
||||||
|
></Button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
{{ data[field] }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<!-- Editor Body -->
|
||||||
|
<template #editor="{ data, field }">
|
||||||
|
<template v-if="field === 'Module'">
|
||||||
<InputText
|
<InputText
|
||||||
v-model="data[field].userDefinedName"
|
v-model="data[field].userDefinedName"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
autofocus
|
autofocus
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="field === 'Reminder'">
|
||||||
|
<!--<InputSwitch
|
||||||
|
v-model="data.Module.reminder"
|
||||||
|
class="align-self-center"
|
||||||
|
/>-->
|
||||||
|
<Button
|
||||||
|
:icon="data.Module.reminder ? 'pi pi-bell' : 'pi pi-times'"
|
||||||
|
:severity="data.Module.reminder ? 'warning' : 'secondary'"
|
||||||
|
rounded
|
||||||
|
outlined
|
||||||
|
class="small-button"
|
||||||
|
@click = "data.Module.reminder = !data.Module.reminder"
|
||||||
|
></Button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div>{{ data[field] }}</div>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
</DataTable>
|
</DataTable>
|
||||||
@@ -100,4 +139,10 @@ async function finalStep() {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
.small-button.p-button {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@@ -19,6 +19,7 @@ const tableData = computed(() =>
|
|||||||
const columns = ref([
|
const columns = ref([
|
||||||
{ field: "Course", header: "Course" },
|
{ field: "Course", header: "Course" },
|
||||||
{ field: "Module", header: "Module" },
|
{ field: "Module", header: "Module" },
|
||||||
|
{ field: "Reminder", header: "Reminder"}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const fetchedModules = async () => {
|
const fetchedModules = async () => {
|
||||||
@@ -56,36 +57,72 @@ async function finalStep() {
|
|||||||
table-class="editable-cells-table"
|
table-class="editable-cells-table"
|
||||||
responsive-layout="scroll"
|
responsive-layout="scroll"
|
||||||
>
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="flex align-items-center justify-content-end">
|
||||||
|
Enable all notifications:
|
||||||
|
<InputSwitch
|
||||||
|
class="mx-4"
|
||||||
|
:model-value="tableData.reduce((acc, curr) => acc && curr.Module.reminder, true)"
|
||||||
|
@update:model-value="tableData.forEach((module) => module.Module.reminder = $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<Column
|
<Column
|
||||||
v-for="col of columns"
|
v-for="col of columns"
|
||||||
:key="col.field"
|
:key="col.field"
|
||||||
:field="col.field"
|
:field="col.field"
|
||||||
:header="col.header"
|
:header="col.header"
|
||||||
|
:class="col.field === 'Reminder' ? 'text-center' : ''"
|
||||||
>
|
>
|
||||||
|
<!-- Text Body -->
|
||||||
<template #body="{ data, field }">
|
<template #body="{ data, field }">
|
||||||
<div>
|
<template v-if="field === 'Module'">
|
||||||
{{
|
{{ data[field].userDefinedName }}
|
||||||
field === "Module" ? data[field].userDefinedName : data[field]
|
</template>
|
||||||
}}
|
<template v-else-if="field === 'Reminder'" class="align-content-center">
|
||||||
</div>
|
<Button
|
||||||
</template>
|
:icon="data.Module.reminder ? 'pi pi-bell' : 'pi pi-times'"
|
||||||
<template #editor="{ data, field }">
|
:severity="data.Module.reminder ? 'warning' : 'secondary'"
|
||||||
<template v-if="field !== 'Module'">
|
rounded
|
||||||
<div>{{ data[field] }}</div>
|
outlined
|
||||||
|
class="small-button"
|
||||||
|
@click = "data.Module.reminder = !data.Module.reminder"
|
||||||
|
></Button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
{{ data[field] }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<!-- Editor Body -->
|
||||||
|
<template #editor="{ data, field }">
|
||||||
|
<template v-if="field === 'Module'">
|
||||||
<InputText
|
<InputText
|
||||||
v-model="data[field].userDefinedName"
|
v-model="data[field].userDefinedName"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
autofocus
|
autofocus
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="field === 'Reminder'">
|
||||||
|
<!--<InputSwitch
|
||||||
|
v-model="data.Module.reminder"
|
||||||
|
class="align-self-center"
|
||||||
|
/>-->
|
||||||
|
<Button
|
||||||
|
:icon="data.Module.reminder ? 'pi pi-bell' : 'pi pi-times'"
|
||||||
|
:severity="data.Module.reminder ? 'warning' : 'secondary'"
|
||||||
|
rounded
|
||||||
|
outlined
|
||||||
|
class="small-button"
|
||||||
|
@click = "data.Module.reminder = !data.Module.reminder"
|
||||||
|
></Button>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column>
|
<Column>
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Button
|
<Button
|
||||||
icon="pi pi-trash"
|
icon="pi pi-trash"
|
||||||
|
class="small-button"
|
||||||
severity="danger"
|
severity="danger"
|
||||||
outlined
|
outlined
|
||||||
rounded
|
rounded
|
||||||
@@ -104,4 +141,10 @@ async function finalStep() {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
.small-button.p-button {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@@ -6,6 +6,7 @@ import Button from "primevue/button";
|
|||||||
import Dropdown from "primevue/dropdown";
|
import Dropdown from "primevue/dropdown";
|
||||||
import Menubar from "primevue/menubar";
|
import Menubar from "primevue/menubar";
|
||||||
import InputText from "primevue/inputtext";
|
import InputText from "primevue/inputtext";
|
||||||
|
import InputSwitch from "primevue/inputswitch";
|
||||||
import Card from "primevue/card";
|
import Card from "primevue/card";
|
||||||
import DataView from "primevue/dataview";
|
import DataView from "primevue/dataview";
|
||||||
import Dialog from "primevue/dialog";
|
import Dialog from "primevue/dialog";
|
||||||
@@ -42,6 +43,7 @@ app.component("Menubar", Menubar);
|
|||||||
app.component("Dialog", Dialog);
|
app.component("Dialog", Dialog);
|
||||||
app.component("Dropdown", Dropdown);
|
app.component("Dropdown", Dropdown);
|
||||||
app.component("InputText", InputText);
|
app.component("InputText", InputText);
|
||||||
|
app.component("InputSwitch", InputSwitch);
|
||||||
app.component("Card", Card);
|
app.component("Card", Card);
|
||||||
app.component("DataView", DataView);
|
app.component("DataView", DataView);
|
||||||
app.component("ToggleButton", ToggleButton);
|
app.component("ToggleButton", ToggleButton);
|
||||||
|
@@ -8,6 +8,7 @@ export class Module {
|
|||||||
public userDefinedName: string,
|
public userDefinedName: string,
|
||||||
public prof: string,
|
public prof: string,
|
||||||
public semester: string,
|
public semester: string,
|
||||||
|
public reminder: boolean,
|
||||||
public events: Event[] = [],
|
public events: Event[] = [],
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user