21 add reminder booleans, map uuid to module

This commit is contained in:
survellow
2023-11-01 19:14:48 +01:00
parent 4e23903b31
commit 8c563e5366
8 changed files with 160 additions and 38 deletions

View File

@@ -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"`
} }

View File

@@ -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)

View File

@@ -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})

View File

@@ -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
} }

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);

View File

@@ -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[] = [],
) {} ) {}