mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender-pwa.git
synced 2025-08-07 04:09:17 +02:00
Merge branch '5-room-finder-for-each-week' into 'main'
Resolve "room finder for each week" Closes #5 See merge request ekresse/htwkalender!12
This commit is contained in:
@@ -46,6 +46,27 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Successful response
|
description: Successful response
|
||||||
|
/api/rooms/free:
|
||||||
|
get:
|
||||||
|
summary: Get Free Rooms
|
||||||
|
parameters:
|
||||||
|
- name: from
|
||||||
|
in: query
|
||||||
|
description: start date
|
||||||
|
example: "2006-01-02T15:04:05Z"
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: to
|
||||||
|
in: query
|
||||||
|
description: end date
|
||||||
|
example: "2006-01-02T15:04:05Z"
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful response
|
||||||
/api/schedule/day:
|
/api/schedule/day:
|
||||||
get:
|
get:
|
||||||
summary: Get Day Schedule
|
summary: Get Day Schedule
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"htwkalender/service/fetch/sport"
|
"htwkalender/service/fetch/sport"
|
||||||
v1 "htwkalender/service/fetch/v1"
|
v1 "htwkalender/service/fetch/v1"
|
||||||
v2 "htwkalender/service/fetch/v2"
|
v2 "htwkalender/service/fetch/v2"
|
||||||
|
"htwkalender/service/functions/time"
|
||||||
"htwkalender/service/ical"
|
"htwkalender/service/ical"
|
||||||
"htwkalender/service/room"
|
"htwkalender/service/room"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
@@ -181,6 +182,39 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// API Endpoint to get all rooms that have no events in a specific time frame
|
||||||
|
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||||
|
_, err := e.Router.AddRoute(echo.Route{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/api/rooms/free",
|
||||||
|
Handler: func(c echo.Context) error {
|
||||||
|
from, err := time.ParseTime(c.QueryParam("from"))
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to parse time: %v", err)
|
||||||
|
return c.JSON(http.StatusBadRequest, "Failed to parse time")
|
||||||
|
}
|
||||||
|
to, err := time.ParseTime(c.QueryParam("to"))
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to parse time: %v", err)
|
||||||
|
return c.JSON(http.StatusBadRequest, "Failed to parse time")
|
||||||
|
}
|
||||||
|
rooms, err := room.GetFreeRooms(app, from, to)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to get free rooms: %v", err)
|
||||||
|
return c.JSON(http.StatusBadRequest, "Failed to get free rooms")
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusOK, rooms)
|
||||||
|
},
|
||||||
|
Middlewares: []echo.MiddlewareFunc{
|
||||||
|
apis.ActivityLogger(app),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
addFeedRoutes(app)
|
addFeedRoutes(app)
|
||||||
|
|
||||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||||
|
@@ -2,6 +2,7 @@ package db
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/pocketbase/pocketbase/tools/types"
|
||||||
"htwkalender/model"
|
"htwkalender/model"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
@@ -10,12 +11,12 @@ import (
|
|||||||
"github.com/pocketbase/pocketbase"
|
"github.com/pocketbase/pocketbase"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SaveSeminarGroupEvents(seminarGroup []model.SeminarGroup, app *pocketbase.PocketBase) ([]model.Event, error) {
|
func SaveSeminarGroupEvents(seminarGroups []model.SeminarGroup, app *pocketbase.PocketBase) ([]model.Event, error) {
|
||||||
var toBeSavedEvents model.Events
|
var toBeSavedEvents model.Events
|
||||||
var savedRecords model.Events
|
var savedRecords model.Events
|
||||||
|
|
||||||
// check if event is already in database and add to toBeSavedEvents if not
|
// check if event is already in database and add to toBeSavedEvents if not
|
||||||
for _, seminarGroup := range seminarGroup {
|
for _, seminarGroup := range seminarGroups {
|
||||||
for _, event := range seminarGroup.Events {
|
for _, event := range seminarGroup.Events {
|
||||||
event = event.SetCourse(seminarGroup.Course)
|
event = event.SetCourse(seminarGroup.Course)
|
||||||
existsInDatabase, err := findEventByDayWeekStartEndNameCourse(event, seminarGroup.Course, app)
|
existsInDatabase, err := findEventByDayWeekStartEndNameCourse(event, seminarGroup.Course, app)
|
||||||
@@ -254,3 +255,89 @@ func GetAllModulesByNameAndDateRange(app *pocketbase.PocketBase, name string, st
|
|||||||
|
|
||||||
return events, nil
|
return events, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetEventsThatCollideWithTimeRange returns all events that collide with the given time range
|
||||||
|
// we have events with start and end in the database, we want to get all events that collide with the given time range
|
||||||
|
// we have 4 cases:
|
||||||
|
// 1. event starts before the given time range and ends after the given time range
|
||||||
|
// 2. event starts after the given time range and ends before the given time range
|
||||||
|
// 3. event starts before the given time range and ends before the given time range
|
||||||
|
// 4. event starts after the given time range and ends after the given time range
|
||||||
|
func GetEventsThatCollideWithTimeRange(app *pocketbase.PocketBase, from time.Time, to time.Time) (model.Events, error) {
|
||||||
|
var fromTypeTime, _ = types.ParseDateTime(from)
|
||||||
|
var toTypeTime, _ = types.ParseDateTime(to)
|
||||||
|
|
||||||
|
events1, err := GetEventsThatStartBeforeAndEndAfter(app, fromTypeTime, toTypeTime)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
events2, err := GetEventsThatStartAfterAndEndBefore(app, fromTypeTime, toTypeTime)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
events3, err := GetEventsThatStartBeforeAndEndBefore(app, fromTypeTime, toTypeTime)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
events4, err := GetEventsThatStartAfterAndEndAfter(app, fromTypeTime, toTypeTime)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var events model.Events
|
||||||
|
events = append(events, events1...)
|
||||||
|
events = append(events, events2...)
|
||||||
|
events = append(events, events3...)
|
||||||
|
events = append(events, events4...)
|
||||||
|
|
||||||
|
return events, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEventsThatStartBeforeAndEndAfter(app *pocketbase.PocketBase, from types.DateTime, to types.DateTime) (model.Events, error) {
|
||||||
|
var events model.Events
|
||||||
|
|
||||||
|
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Start <= {:startDate} AND End >= {:endDate} AND Start <= {:endDate} AND End >= {:startDate}", dbx.Params{"startDate": from, "endDate": to})).Distinct(true).All(&events)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return events, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEventsThatStartAfterAndEndBefore(app *pocketbase.PocketBase, from types.DateTime, to types.DateTime) (model.Events, error) {
|
||||||
|
var events model.Events
|
||||||
|
|
||||||
|
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Start >= {:startDate} AND End <= {:endDate} AND Start <= {:endDate} AND End >= {:startDate}", dbx.Params{"startDate": from, "endDate": to})).All(&events)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return events, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEventsThatStartBeforeAndEndBefore(app *pocketbase.PocketBase, from types.DateTime, to types.DateTime) (model.Events, error) {
|
||||||
|
var events model.Events
|
||||||
|
|
||||||
|
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Start <= {:startDate} AND End <= {:endDate} AND Start <= {:endDate} AND End >= {:startDate}", dbx.Params{"startDate": from, "endDate": to})).All(&events)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return events, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEventsThatStartAfterAndEndAfter(app *pocketbase.PocketBase, from types.DateTime, to types.DateTime) (model.Events, error) {
|
||||||
|
var events model.Events
|
||||||
|
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Start >= {:startDate} AND End >= {:endDate} AND Start <= {:endDate} AND End >= {:startDate}", dbx.Params{"startDate": from, "endDate": to})).All(&events)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return events, nil
|
||||||
|
}
|
||||||
|
@@ -47,9 +47,7 @@ func clearAndSeparateRooms(events []struct {
|
|||||||
// sport rooms don't have to be separated
|
// sport rooms don't have to be separated
|
||||||
if event.Course != "Sport" {
|
if event.Course != "Sport" {
|
||||||
//split rooms by comma, tab, newline, carriage return, semicolon, space and non-breaking space
|
//split rooms by comma, tab, newline, carriage return, semicolon, space and non-breaking space
|
||||||
room = strings.FieldsFunc(event.Rooms, functions.IsSeparator(
|
room = functions.SeperateRoomString(event.Rooms)
|
||||||
[]rune{',', '\t', '\n', '\r', ';', ' ', '\u00A0'}),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
room = append(room, event.Rooms)
|
room = append(room, event.Rooms)
|
||||||
}
|
}
|
||||||
|
@@ -45,3 +45,9 @@ func HashString(s string) string {
|
|||||||
hashInBytes := hash.Sum(nil)
|
hashInBytes := hash.Sum(nil)
|
||||||
return hex.EncodeToString(hashInBytes)
|
return hex.EncodeToString(hashInBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SeperateRoomString(rooms string) []string {
|
||||||
|
return strings.FieldsFunc(rooms, IsSeparator(
|
||||||
|
[]rune{',', '\t', '\n', '\r', ';', ' ', '\u00A0'}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
7
backend/service/functions/time/parse.go
Normal file
7
backend/service/functions/time/parse.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package time
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
func ParseTime(timeString string) (time.Time, error) {
|
||||||
|
return time.Parse("2006-01-02T15:04:05Z", timeString)
|
||||||
|
}
|
@@ -4,6 +4,8 @@ import (
|
|||||||
"github.com/pocketbase/pocketbase"
|
"github.com/pocketbase/pocketbase"
|
||||||
"htwkalender/model"
|
"htwkalender/model"
|
||||||
"htwkalender/service/db"
|
"htwkalender/service/db"
|
||||||
|
"htwkalender/service/functions"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetRooms(app *pocketbase.PocketBase) ([]string, error) {
|
func GetRooms(app *pocketbase.PocketBase) ([]string, error) {
|
||||||
@@ -41,3 +43,46 @@ func anonymizeRooms(events []model.Event) []model.AnonymizedEventDTO {
|
|||||||
}
|
}
|
||||||
return anonymizedEvents
|
return anonymizedEvents
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetFreeRooms(app *pocketbase.PocketBase, from time.Time, to time.Time) ([]string, error) {
|
||||||
|
rooms, err := db.GetRooms(app)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var events model.Events
|
||||||
|
events, err = db.GetEventsThatCollideWithTimeRange(app, from, to)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
freeRooms := removeRoomsThatHaveEvents(rooms, events)
|
||||||
|
return freeRooms, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeRoomsThatHaveEvents(rooms []string, schedule []model.Event) []string {
|
||||||
|
var freeRooms []string
|
||||||
|
for _, room := range rooms {
|
||||||
|
if !isRoomInSchedule(room, schedule) {
|
||||||
|
freeRooms = append(freeRooms, room)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return freeRooms
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRoomInSchedule(room string, schedule []model.Event) bool {
|
||||||
|
for _, event := range schedule {
|
||||||
|
if event.Course != "Sport" {
|
||||||
|
rooms := functions.SeperateRoomString(event.Rooms)
|
||||||
|
// check if room is in rooms
|
||||||
|
for _, r := range rooms {
|
||||||
|
if r == room {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if event.Rooms == room {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
package room
|
package room
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/pocketbase/pocketbase/tools/types"
|
||||||
"htwkalender/model"
|
"htwkalender/model"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase/tools/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_anonymizeRooms(t *testing.T) {
|
func Test_anonymizeRooms(t *testing.T) {
|
||||||
@@ -123,3 +122,170 @@ func Test_anonymizeRooms(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_isRoomInSchedule(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
room string
|
||||||
|
schedule []model.Event
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "room is in schedule",
|
||||||
|
args: args{
|
||||||
|
room: "Room",
|
||||||
|
schedule: []model.Event{
|
||||||
|
{
|
||||||
|
UUID: "testUUID",
|
||||||
|
Day: "Montag",
|
||||||
|
Week: "52",
|
||||||
|
Start: types.DateTime{},
|
||||||
|
End: types.DateTime{},
|
||||||
|
Name: "Secret",
|
||||||
|
EventType: "V",
|
||||||
|
Prof: "Prof. Dr. Secret",
|
||||||
|
Rooms: "Room",
|
||||||
|
Notes: "Secret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "room is not in schedule",
|
||||||
|
args: args{
|
||||||
|
room: "Z324",
|
||||||
|
schedule: []model.Event{
|
||||||
|
{
|
||||||
|
UUID: "testUUID",
|
||||||
|
Day: "Montag",
|
||||||
|
Week: "52",
|
||||||
|
Start: types.DateTime{},
|
||||||
|
End: types.DateTime{},
|
||||||
|
Name: "Secret",
|
||||||
|
EventType: "V",
|
||||||
|
Prof: "Prof. Dr. Bond",
|
||||||
|
Rooms: "LI007",
|
||||||
|
Notes: "Keine Zeit für die Uni",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := isRoomInSchedule(tt.args.room, tt.args.schedule); got != tt.want {
|
||||||
|
t.Errorf("isRoomInSchedule() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_getFreeRooms(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
rooms []string
|
||||||
|
schedule []model.Event
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "remove room1 from list",
|
||||||
|
args: args{
|
||||||
|
rooms: []string{
|
||||||
|
"Room1",
|
||||||
|
"Room2",
|
||||||
|
"Room3",
|
||||||
|
},
|
||||||
|
schedule: []model.Event{
|
||||||
|
{
|
||||||
|
UUID: "testUUID",
|
||||||
|
Day: "Montag",
|
||||||
|
Week: "52",
|
||||||
|
Start: types.DateTime{},
|
||||||
|
End: types.DateTime{},
|
||||||
|
Name: "Secret",
|
||||||
|
EventType: "V",
|
||||||
|
Prof: "Prof. Dr. Secret",
|
||||||
|
Rooms: "Room1",
|
||||||
|
Notes: "Secret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []string{
|
||||||
|
"Room2",
|
||||||
|
"Room3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remove room2 from list",
|
||||||
|
args: args{
|
||||||
|
rooms: []string{
|
||||||
|
"Room1",
|
||||||
|
"Room2",
|
||||||
|
"Room3",
|
||||||
|
},
|
||||||
|
schedule: []model.Event{
|
||||||
|
{
|
||||||
|
UUID: "testUUID",
|
||||||
|
Day: "Montag",
|
||||||
|
Week: "52",
|
||||||
|
Start: types.DateTime{},
|
||||||
|
End: types.DateTime{},
|
||||||
|
Name: "Secret",
|
||||||
|
EventType: "V",
|
||||||
|
Prof: "Prof. Dr. Secret",
|
||||||
|
Rooms: "Room3",
|
||||||
|
Notes: "Secret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []string{
|
||||||
|
"Room1",
|
||||||
|
"Room2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remove no room from list",
|
||||||
|
args: args{
|
||||||
|
rooms: []string{
|
||||||
|
"Room1",
|
||||||
|
"Room2",
|
||||||
|
"Room3",
|
||||||
|
},
|
||||||
|
schedule: []model.Event{
|
||||||
|
{
|
||||||
|
UUID: "testUUID",
|
||||||
|
Day: "Montag",
|
||||||
|
Week: "52",
|
||||||
|
Start: types.DateTime{},
|
||||||
|
End: types.DateTime{},
|
||||||
|
Name: "Secret",
|
||||||
|
EventType: "V",
|
||||||
|
Prof: "Prof. Dr. Secret",
|
||||||
|
Rooms: "Room4",
|
||||||
|
Notes: "Secret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []string{
|
||||||
|
"Room1",
|
||||||
|
"Room2",
|
||||||
|
"Room3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := removeRoomsThatHaveEvents(tt.args.rooms, tt.args.schedule); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("removeRoomsThatHaveEvents() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
11
frontend/package-lock.json
generated
11
frontend/package-lock.json
generated
@@ -18,7 +18,7 @@
|
|||||||
"primeflex": "^3.3.1",
|
"primeflex": "^3.3.1",
|
||||||
"primeicons": "^6.0.1",
|
"primeicons": "^6.0.1",
|
||||||
"primevue": "^3.46.0",
|
"primevue": "^3.46.0",
|
||||||
"source-sans-pro": "^3.6.0",
|
"source-sans": "^3.46.0",
|
||||||
"vue": "^3.4.11",
|
"vue": "^3.4.11",
|
||||||
"vue-i18n": "^9.9.0",
|
"vue-i18n": "^9.9.0",
|
||||||
"vue-router": "^4.2.5"
|
"vue-router": "^4.2.5"
|
||||||
@@ -3967,11 +3967,10 @@
|
|||||||
"source-map": "^0.6.0"
|
"source-map": "^0.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-sans-pro": {
|
"node_modules/source-sans": {
|
||||||
"version": "3.6.0",
|
"version": "3.46.0",
|
||||||
"resolved": "https://registry.npmjs.org/source-sans-pro/-/source-sans-pro-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/source-sans/-/source-sans-3.46.0.tgz",
|
||||||
"integrity": "sha512-C1RFUGu+YASuqpgDRInTM7Y6OwqeWNOuKn7v0P/4Kh66epTI4PYWwPWP5kdA4l/VqzBAWiqoz5dk0trof73R7w==",
|
"integrity": "sha512-bVC2YX4VNiv5vMcy77dL0XKsNp794ThfynNsr+FqSAwk8TGG0pZsg7eUQi6yHwaRBMVmZ3Aaf4FY46dxIIGgsg=="
|
||||||
"deprecated": "WARNING: This project has been renamed to source-sans. Install using source-sans instead."
|
|
||||||
},
|
},
|
||||||
"node_modules/stackback": {
|
"node_modules/stackback": {
|
||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
|
@@ -23,7 +23,7 @@
|
|||||||
"primeflex": "^3.3.1",
|
"primeflex": "^3.3.1",
|
||||||
"primeicons": "^6.0.1",
|
"primeicons": "^6.0.1",
|
||||||
"primevue": "^3.46.0",
|
"primevue": "^3.46.0",
|
||||||
"source-sans-pro": "^3.6.0",
|
"source-sans": "^3.46.0",
|
||||||
"vue": "^3.4.11",
|
"vue": "^3.4.11",
|
||||||
"vue-i18n": "^9.9.0",
|
"vue-i18n": "^9.9.0",
|
||||||
"vue-router": "^4.2.5"
|
"vue-router": "^4.2.5"
|
||||||
|
File diff suppressed because it is too large
Load Diff
1
frontend/public/themes/lara-dark-blue/theme.css.map
Normal file
1
frontend/public/themes/lara-dark-blue/theme.css.map
Normal file
File diff suppressed because one or more lines are too long
7484
frontend/public/themes/lara-dark-blue/theme.scss
Normal file
7484
frontend/public/themes/lara-dark-blue/theme.scss
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,8 +6,8 @@ export async function fetchRoom(): Promise<string[]> {
|
|||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then((roomsResponse) => {
|
.then((roomsResponse: [] | null) => {
|
||||||
roomsResponse.forEach((room: string) => rooms.push(room));
|
roomsResponse?.forEach((room: string) => rooms.push(room));
|
||||||
});
|
});
|
||||||
return rooms;
|
return rooms;
|
||||||
}
|
}
|
||||||
@@ -22,17 +22,15 @@ export async function fetchEventsByRoomAndDuration(
|
|||||||
"/api/schedule?room=" + room + "&from=" + from_date + "&to=" + to_date,
|
"/api/schedule?room=" + room + "&from=" + from_date + "&to=" + to_date,
|
||||||
)
|
)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
console.log(response);
|
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then((eventsResponse) => {
|
.then((eventsResponse: [] | null) => {
|
||||||
console.log("Response:", eventsResponse);
|
eventsResponse?.forEach((event: AnonymizedEventDTO) =>
|
||||||
eventsResponse.forEach((event: AnonymizedEventDTO) => events.push(event));
|
events.push(event),
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log("Error fetching events: ", error);
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
});
|
});
|
||||||
console.log("occupations: ", events);
|
|
||||||
return events;
|
return events;
|
||||||
}
|
}
|
||||||
|
17
frontend/src/api/requestFreeRooms.ts
Normal file
17
frontend/src/api/requestFreeRooms.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// load free rooms as a list of strings form the backend
|
||||||
|
|
||||||
|
export async function requestFreeRooms(
|
||||||
|
from: string,
|
||||||
|
to: string,
|
||||||
|
): Promise<string[]> {
|
||||||
|
console.debug("requestFreeRooms: from=" + from + ", to=" + to);
|
||||||
|
const rooms: string[] = [];
|
||||||
|
await fetch("/api/rooms/free?from=" + from + "&to=" + to)
|
||||||
|
.then((response) => {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((roomsResponse: [] | null) => {
|
||||||
|
roomsResponse?.forEach((room: string) => rooms.push(room));
|
||||||
|
});
|
||||||
|
return rooms;
|
||||||
|
}
|
@@ -208,6 +208,16 @@
|
|||||||
<div class="col">{{ $t("faqView.eighthQuestion") }}</div>
|
<div class="col">{{ $t("faqView.eighthQuestion") }}</div>
|
||||||
<div class="col">{{ $t("faqView.eighthAnswer") }}</div>
|
<div class="col">{{ $t("faqView.eighthAnswer") }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid my-2">
|
||||||
|
<div class="col">{{ $t("faqView.ninthQuestion") }}</div>
|
||||||
|
<div class="col">
|
||||||
|
{{ $t("faqView.ninthAnswer") }}
|
||||||
|
<a href="https://git.imn.htwk-leipzig.de/ekresse/htwkalender"
|
||||||
|
>Gitlab</a
|
||||||
|
>
|
||||||
|
.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<p>
|
<p>
|
||||||
{{ $t("faqView.notFound") }}<br />
|
{{ $t("faqView.notFound") }}<br />
|
||||||
<a href="/imprint">{{ $t("faqView.contact") }}</a>
|
<a href="/imprint">{{ $t("faqView.contact") }}</a>
|
||||||
|
@@ -2,7 +2,9 @@
|
|||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import localeStore from "../store/localeStore.ts";
|
import localeStore from "../store/localeStore.ts";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { DropdownChangeEvent } from "primevue/dropdown";
|
import { usePrimeVue } from "primevue/config";
|
||||||
|
import primeVue_de from "@/i18n/translations/primevue/prime_vue_local_de.json";
|
||||||
|
import primeVue_en from "@/i18n/translations/primevue/prime_vue_local_en.json";
|
||||||
const { t } = useI18n({ useScope: "global" });
|
const { t } = useI18n({ useScope: "global" });
|
||||||
|
|
||||||
const countries = computed(() => [
|
const countries = computed(() => [
|
||||||
@@ -18,9 +20,19 @@ function displayCountry(code: string) {
|
|||||||
return countries.value.find((country) => country.code === code)?.name;
|
return countries.value.find((country) => country.code === code)?.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateLocale(dropdownChangeEvent: DropdownChangeEvent) {
|
const primeVueConfig = usePrimeVue();
|
||||||
localeStore().setLocale(dropdownChangeEvent.value);
|
|
||||||
|
function updateLocale(locale: string) {
|
||||||
|
localeStore().setLocale(locale);
|
||||||
|
|
||||||
|
if (locale === "de") {
|
||||||
|
primeVueConfig.config.locale = primeVue_de;
|
||||||
|
} else {
|
||||||
|
primeVueConfig.config.locale = primeVue_en;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateLocale(localeStore().locale);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@@ -29,7 +41,7 @@ function updateLocale(dropdownChangeEvent: DropdownChangeEvent) {
|
|||||||
option-label="name"
|
option-label="name"
|
||||||
placeholder="Select a Language"
|
placeholder="Select a Language"
|
||||||
class="w-full md:w-14rem"
|
class="w-full md:w-14rem"
|
||||||
@change="updateLocale($event)"
|
@change="updateLocale($event.value)"
|
||||||
>
|
>
|
||||||
<template #value="slotProps">
|
<template #value="slotProps">
|
||||||
<div v-if="slotProps.value" class="flex align-items-center">
|
<div v-if="slotProps.value" class="flex align-items-center">
|
||||||
|
@@ -17,9 +17,21 @@ const items = computed(() => [
|
|||||||
route: "/edit",
|
route: "/edit",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("roomFinder"),
|
label: t("rooms"),
|
||||||
icon: "pi pi-fw pi-calendar",
|
icon: "pi pi-fw pi-angle-down",
|
||||||
route: "/rooms",
|
info: "rooms",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: t("roomFinderPage.roomSchedule"),
|
||||||
|
icon: "pi pi-fw pi-hourglass",
|
||||||
|
route: "/rooms/occupancy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("freeRooms.freeRooms"),
|
||||||
|
icon: "pi pi-fw pi-calendar",
|
||||||
|
route: "/rooms/free",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("faq"),
|
label: t("faq"),
|
||||||
@@ -44,26 +56,42 @@ const items = computed(() => [
|
|||||||
<template #start>
|
<template #start>
|
||||||
<router-link v-slot="{ navigate }" :to="`/`" custom>
|
<router-link v-slot="{ navigate }" :to="`/`" custom>
|
||||||
<Button severity="secondary" text class="p-0 mx-2" @click="navigate">
|
<Button severity="secondary" text class="p-0 mx-2" @click="navigate">
|
||||||
<img
|
<img width="50" height="50" src="../../public/htwk.svg" alt="Logo" />
|
||||||
width="50"
|
|
||||||
height="50"
|
|
||||||
src="../../public/htwk.svg"
|
|
||||||
alt="Logo"
|
|
||||||
/>
|
|
||||||
</Button>
|
</Button>
|
||||||
</router-link>
|
</router-link>
|
||||||
</template>
|
</template>
|
||||||
<template #item="{ item }">
|
<template #item="{ item, props }">
|
||||||
<router-link v-slot="{ navigate }" :to="item.route" custom>
|
<router-link
|
||||||
<Button
|
v-if="item.route"
|
||||||
:label="String(item.label)"
|
v-slot="{ navigate }"
|
||||||
:icon="item.icon"
|
:to="item.route"
|
||||||
text
|
custom
|
||||||
severity="secondary"
|
>
|
||||||
:class="item.route === $route.path ? 'active' : ''"
|
<a
|
||||||
|
:class="
|
||||||
|
$route.path == item.route
|
||||||
|
? 'flex align-items-center active'
|
||||||
|
: 'flex align-items-center'
|
||||||
|
"
|
||||||
|
v-bind="props.action"
|
||||||
@click="navigate"
|
@click="navigate"
|
||||||
/>
|
>
|
||||||
|
<span :class="item.icon" />
|
||||||
|
<span class="ml-2 p-menuitem-label">{{ item.label }}</span>
|
||||||
|
</a>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<a
|
||||||
|
v-else
|
||||||
|
:class="
|
||||||
|
$route.path.includes(item.info)
|
||||||
|
? 'flex align-items-center active'
|
||||||
|
: 'flex align-items-center'
|
||||||
|
"
|
||||||
|
v-bind="props.action"
|
||||||
|
>
|
||||||
|
<span :class="item.icon" />
|
||||||
|
<span class="ml-2 p-menuitem-label">{{ item.label }}</span>
|
||||||
|
</a>
|
||||||
</template>
|
</template>
|
||||||
<template #end>
|
<template #end>
|
||||||
<div class="flex align-items-stretch justify-content-center">
|
<div class="flex align-items-stretch justify-content-center">
|
||||||
@@ -80,7 +108,11 @@ const items = computed(() => [
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.p-button .p-button-label::after) {
|
:deep(.p-submenu-list) {
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.p-menuitem-link .p-menuitem-label::after) {
|
||||||
content: "";
|
content: "";
|
||||||
display: block;
|
display: block;
|
||||||
width: 0;
|
width: 0;
|
||||||
@@ -89,7 +121,7 @@ const items = computed(() => [
|
|||||||
transition: width 0.3s;
|
transition: width 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.p-button.active .p-button-label::after) {
|
:deep(.p-menuitem-link.active .p-menuitem-label::after) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -47,7 +47,7 @@ async function getOccupation() {
|
|||||||
currentDateTo.value,
|
currentDateTo.value,
|
||||||
)
|
)
|
||||||
.then((events) => {
|
.then((events) => {
|
||||||
occupations.value = events.map((event, index) => {
|
occupations.value = events?.map((event, index) => {
|
||||||
return {
|
return {
|
||||||
id: index,
|
id: index,
|
||||||
start: event.start.replace(/\s\+\d{4}\s\w+$/, "").replace(" ", "T"),
|
start: event.start.replace(/\s\+\d{4}\s\w+$/, "").replace(" ", "T"),
|
||||||
|
@@ -3,11 +3,8 @@ import en from "./translations/en.json";
|
|||||||
import de from "./translations/de.json";
|
import de from "./translations/de.json";
|
||||||
import localeStore from "../store/localeStore.ts";
|
import localeStore from "../store/localeStore.ts";
|
||||||
|
|
||||||
export const supportedLocales = {
|
|
||||||
en: { name: "English" },
|
|
||||||
de: { name: "Deutsch" },
|
|
||||||
};
|
|
||||||
// Private instance of VueI18n object
|
// Private instance of VueI18n object
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
let _i18n: any;
|
let _i18n: any;
|
||||||
// Initializer
|
// Initializer
|
||||||
function setup() {
|
function setup() {
|
||||||
|
@@ -1,18 +0,0 @@
|
|||||||
export default {
|
|
||||||
en: {
|
|
||||||
createCalendar: "Create Calendar",
|
|
||||||
editCalendar: "Edit Calendar",
|
|
||||||
roomFinder: "Room Finder",
|
|
||||||
faq: "FAQ",
|
|
||||||
imprint: "Imprint",
|
|
||||||
privacy: "Privacy Policy",
|
|
||||||
},
|
|
||||||
de: {
|
|
||||||
createCalendar: "Kalender erstellen",
|
|
||||||
editCalendar: "Kalender bearbeiten",
|
|
||||||
roomFinder: "Raumfinder",
|
|
||||||
faq: "FAQ",
|
|
||||||
imprint: "Impressum",
|
|
||||||
privacy: "Datenschutz",
|
|
||||||
},
|
|
||||||
};
|
|
@@ -2,7 +2,7 @@
|
|||||||
"languageCode": "de",
|
"languageCode": "de",
|
||||||
"createCalendar": "Kalender erstellen",
|
"createCalendar": "Kalender erstellen",
|
||||||
"editCalendar": "Kalender bearbeiten",
|
"editCalendar": "Kalender bearbeiten",
|
||||||
"roomFinder": "Raumfinder",
|
"rooms": "Räume",
|
||||||
"faq": "FAQ",
|
"faq": "FAQ",
|
||||||
"imprint": "Impressum",
|
"imprint": "Impressum",
|
||||||
"privacy": "Datenschutz",
|
"privacy": "Datenschutz",
|
||||||
@@ -19,13 +19,22 @@
|
|||||||
"semesterDropDown": "Semester"
|
"semesterDropDown": "Semester"
|
||||||
},
|
},
|
||||||
"roomFinderPage": {
|
"roomFinderPage": {
|
||||||
"headline": "Raumfinder",
|
"roomSchedule": "Raumbelegung",
|
||||||
|
"headline": "Raumbelegung",
|
||||||
"detail": "Bitte wähle einen Raum aus, um die Belegung einzusehen",
|
"detail": "Bitte wähle einen Raum aus, um die Belegung einzusehen",
|
||||||
"dropDownSelect": "Bitte wähle einen Raum aus",
|
"dropDownSelect": "Bitte wähle einen Raum aus",
|
||||||
"noRoomsAvailable": "Keine Räume verfügbar",
|
"noRoomsAvailable": "Keine Räume verfügbar",
|
||||||
"available": "verfügbar",
|
"available": "verfügbar",
|
||||||
"occupied": "belegt"
|
"occupied": "belegt"
|
||||||
},
|
},
|
||||||
|
"freeRooms": {
|
||||||
|
"freeRooms": "Freie Räume",
|
||||||
|
"detail": "Bitte wähle einen Zeitraum aus, um alle Räume ohne Belegung anzuzeigen.",
|
||||||
|
"searchByRoom": "Suche nach Räumen",
|
||||||
|
"pleaseSelectDate": "Bitte wähle ein Datum aus",
|
||||||
|
"room": "Raum",
|
||||||
|
"search": "Suchen"
|
||||||
|
},
|
||||||
"moduleSelection": {
|
"moduleSelection": {
|
||||||
"selectAll": "Alle anwählen",
|
"selectAll": "Alle anwählen",
|
||||||
"deselectAll": "Alle abwählen",
|
"deselectAll": "Alle abwählen",
|
||||||
@@ -221,6 +230,8 @@
|
|||||||
"seventhAnswer": "Studenpläne sind erstmal nur für die ausgewählten Semester gültig. Da durch Wahlpflichtmodule oder deine Planung sich Veränderungen ergeben können.",
|
"seventhAnswer": "Studenpläne sind erstmal nur für die ausgewählten Semester gültig. Da durch Wahlpflichtmodule oder deine Planung sich Veränderungen ergeben können.",
|
||||||
"eighthQuestion": "Preis und Entwicklung?",
|
"eighthQuestion": "Preis und Entwicklung?",
|
||||||
"eighthAnswer": "Die Kosten können durch das selbständiges Hosting vollständig ausgelagert werden. Die Entwicklung soll als aktives Git Projekt auch durch die Community verwaltet werden.",
|
"eighthAnswer": "Die Kosten können durch das selbständiges Hosting vollständig ausgelagert werden. Die Entwicklung soll als aktives Git Projekt auch durch die Community verwaltet werden.",
|
||||||
|
"ninthQuestion": "Wo kann ich den Quellcode einsehen und mitwirken?",
|
||||||
|
"ninthAnswer": "Wenn du dich für die Entwicklung und den Quelltext interessierst, kannst du jederzeit als HTWK-Student daran mitarbeiten. Quelltext und weitere Informationen findest du im ",
|
||||||
"notFound": "Nicht gefunden, wonach du suchst?",
|
"notFound": "Nicht gefunden, wonach du suchst?",
|
||||||
"contact": "Kontakt aufnehmen"
|
"contact": "Kontakt aufnehmen"
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"languageCode": "en",
|
"languageCode": "en",
|
||||||
"createCalendar": "create calendar",
|
"createCalendar": "create calendar",
|
||||||
"editCalendar": "edit calendar",
|
"editCalendar": "edit calendar",
|
||||||
"roomFinder": "room finder",
|
"rooms": "rooms",
|
||||||
"faq": "faq",
|
"faq": "faq",
|
||||||
"imprint": "imprint",
|
"imprint": "imprint",
|
||||||
"privacy": "privacy",
|
"privacy": "privacy",
|
||||||
@@ -19,13 +19,22 @@
|
|||||||
"semesterDropDown": "please select a semester"
|
"semesterDropDown": "please select a semester"
|
||||||
},
|
},
|
||||||
"roomFinderPage": {
|
"roomFinderPage": {
|
||||||
"headline": "room finder",
|
"roomSchedule": "room occupancy",
|
||||||
"detail": "please select a room to view the occupancy",
|
"headline": "room occupancy plan",
|
||||||
|
"detail": "Please select a room to view the occupancy.",
|
||||||
"dropDownSelect": "please select a room",
|
"dropDownSelect": "please select a room",
|
||||||
"noRoomsAvailable": "no rooms listed",
|
"noRoomsAvailable": "no rooms listed",
|
||||||
"available": "available",
|
"available": "available",
|
||||||
"occupied": "occupied"
|
"occupied": "occupied"
|
||||||
},
|
},
|
||||||
|
"freeRooms": {
|
||||||
|
"freeRooms": "free rooms",
|
||||||
|
"detail": "Please select a time period to display rooms that have no occupancy.",
|
||||||
|
"searchByRoom": "search by room",
|
||||||
|
"pleaseSelectDate": "please select a date",
|
||||||
|
"room": "room",
|
||||||
|
"search": "search"
|
||||||
|
},
|
||||||
"moduleSelection": {
|
"moduleSelection": {
|
||||||
"selectAll": "select all",
|
"selectAll": "select all",
|
||||||
"deselectAll": "deselect all",
|
"deselectAll": "deselect all",
|
||||||
@@ -221,6 +230,8 @@
|
|||||||
"seventhAnswer": "Timetables are initially only valid for the selected semesters, as changes can occur due to elective modules or your planning.",
|
"seventhAnswer": "Timetables are initially only valid for the selected semesters, as changes can occur due to elective modules or your planning.",
|
||||||
"eighthQuestion": "Cost and development?",
|
"eighthQuestion": "Cost and development?",
|
||||||
"eighthAnswer": "Costs can be completely outsourced through self-hosting. Development is intended to be managed by the community as an active Git project.",
|
"eighthAnswer": "Costs can be completely outsourced through self-hosting. Development is intended to be managed by the community as an active Git project.",
|
||||||
|
"ninthQuestion": "Where could i find the source code?",
|
||||||
|
"ninthAnswer": "If you want to contribute, you can do so at any time if you are a HTWK student. The source code is available on ",
|
||||||
"notFound": "Not finding what you're looking for?",
|
"notFound": "Not finding what you're looking for?",
|
||||||
"contact": "Get in touch"
|
"contact": "Get in touch"
|
||||||
}
|
}
|
||||||
|
155
frontend/src/i18n/translations/primevue/prime_vue_local_de.json
Normal file
155
frontend/src/i18n/translations/primevue/prime_vue_local_de.json
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
{
|
||||||
|
"startsWith": "Beginnt mit",
|
||||||
|
"contains": "Enthält",
|
||||||
|
"notContains": "Enthält nicht",
|
||||||
|
"endsWith": "Endet mit",
|
||||||
|
"equals": "Ist gleich",
|
||||||
|
"notEquals": "Ist ungleich",
|
||||||
|
"noFilter": "Kein Filter",
|
||||||
|
"filter": "Filtern",
|
||||||
|
"lt": "Kleiner als",
|
||||||
|
"lte": "Kleiner oder gleich",
|
||||||
|
"gt": "Größer als",
|
||||||
|
"gte": "Größer oder gleich",
|
||||||
|
"dateIs": "Datum ist",
|
||||||
|
"dateIsNot": "Datum ist nicht",
|
||||||
|
"dateBefore": "Datum ist vor",
|
||||||
|
"dateAfter": "Datum ist nach",
|
||||||
|
"custom": "Benutzerdefiniert",
|
||||||
|
"clear": "Löschen",
|
||||||
|
"apply": "Übernehmen",
|
||||||
|
"matchAll": "Passt auf alle",
|
||||||
|
"matchAny": "Passt auf einige",
|
||||||
|
"addRule": "Regel hinzufügen",
|
||||||
|
"removeRule": "Regel entfernen",
|
||||||
|
"accept": "Ja",
|
||||||
|
"reject": "Nein",
|
||||||
|
"choose": "Auswählen",
|
||||||
|
"upload": "Hochladen",
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"completed": "Abgeschlossen",
|
||||||
|
"pending": "Ausstehend",
|
||||||
|
"fileSizeTypes": ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
|
||||||
|
"dayNames": [
|
||||||
|
"Sonntag",
|
||||||
|
"Montag",
|
||||||
|
"Dienstag",
|
||||||
|
"Mittwoch",
|
||||||
|
"Donnerstag",
|
||||||
|
"Freitag",
|
||||||
|
"Samstag"
|
||||||
|
],
|
||||||
|
"dayNamesShort": ["Son", "Mon", "Die", "Mit", "Don", "Fre", "Sam"],
|
||||||
|
"dayNamesMin": ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"],
|
||||||
|
"monthNames": [
|
||||||
|
"Januar",
|
||||||
|
"Februar",
|
||||||
|
"März",
|
||||||
|
"April",
|
||||||
|
"Mai",
|
||||||
|
"Juni",
|
||||||
|
"Juli",
|
||||||
|
"August",
|
||||||
|
"September",
|
||||||
|
"Oktober",
|
||||||
|
"November",
|
||||||
|
"Dezember"
|
||||||
|
],
|
||||||
|
"monthNamesShort": [
|
||||||
|
"Jan",
|
||||||
|
"Feb",
|
||||||
|
"Mär",
|
||||||
|
"Apr",
|
||||||
|
"Mai",
|
||||||
|
"Jun",
|
||||||
|
"Jul",
|
||||||
|
"Aug",
|
||||||
|
"Sep",
|
||||||
|
"Okt",
|
||||||
|
"Nov",
|
||||||
|
"Dez"
|
||||||
|
],
|
||||||
|
"chooseYear": "Jahr wählen",
|
||||||
|
"chooseMonth": "Monat wählen",
|
||||||
|
"chooseDate": "Datum wählen",
|
||||||
|
"prevDecade": "Vorheriges Jahrzehnt",
|
||||||
|
"nextDecade": "Nächstes Jahrzehnt",
|
||||||
|
"prevYear": "Vorheriges Jahr",
|
||||||
|
"nextYear": "Nächstes Jahr",
|
||||||
|
"prevMonth": "Vorheriger Monat",
|
||||||
|
"nextMonth": "Nächster Monat",
|
||||||
|
"prevHour": "Vorherige Stunde",
|
||||||
|
"nextHour": "Nächste Stunde",
|
||||||
|
"prevMinute": "Vorherige Minute",
|
||||||
|
"nextMinute": "Nächste Minute",
|
||||||
|
"prevSecond": "Vorherige Sekunde",
|
||||||
|
"nextSecond": "Nächste Sekunde",
|
||||||
|
"am": "am",
|
||||||
|
"pm": "pm",
|
||||||
|
"today": "Heute",
|
||||||
|
"now": "Jetzt",
|
||||||
|
"weekHeader": "KW",
|
||||||
|
"firstDayOfWeek": 1,
|
||||||
|
"showMonthAfterYear": false,
|
||||||
|
"dateFormat": "dd.mm.yy",
|
||||||
|
"weak": "Schwach",
|
||||||
|
"medium": "Mittel",
|
||||||
|
"strong": "Stark",
|
||||||
|
"passwordPrompt": "Passwort eingeben",
|
||||||
|
"emptyFilterMessage": "Keine Ergebnisse gefunden",
|
||||||
|
"searchMessage": "{0} Ergebnisse verfügbar",
|
||||||
|
"selectionMessage": "{0} Elemente ausgewählt",
|
||||||
|
"emptySelectionMessage": "Kein ausgewähltes Element",
|
||||||
|
"emptySearchMessage": "Keine Ergebnisse gefunden",
|
||||||
|
"emptyMessage": "Keine Einträge gefunden",
|
||||||
|
"aria": {
|
||||||
|
"trueLabel": "Wahr",
|
||||||
|
"falseLabel": "Falsch",
|
||||||
|
"nullLabel": "Nicht ausgewählt",
|
||||||
|
"star": "1 Stern",
|
||||||
|
"stars": "{star} Sterne",
|
||||||
|
"selectAll": "Alle Elemente ausgewählt",
|
||||||
|
"unselectAll": "Alle Elemente abgewählt",
|
||||||
|
"close": "Schließen",
|
||||||
|
"previous": "Vorherige",
|
||||||
|
"next": "Nächste",
|
||||||
|
"navigation": "Navigation",
|
||||||
|
"scrollTop": "Nach oben scrollen",
|
||||||
|
"moveTop": "Zum Anfang bewegen",
|
||||||
|
"moveUp": "Nach oben bewegen",
|
||||||
|
"moveDown": "Nach unten bewegen",
|
||||||
|
"moveBottom": "Zum Ende bewegen",
|
||||||
|
"moveToTarget": "Zum Ziel bewegen",
|
||||||
|
"moveToSource": "Zur Quelle bewegen",
|
||||||
|
"moveAllToTarget": "Alle zum Ziel bewegen",
|
||||||
|
"moveAllToSource": "Alle zur Quelle bewegen",
|
||||||
|
"pageLabel": "Seite {page}",
|
||||||
|
"firstPageLabel": "Erste Seite",
|
||||||
|
"lastPageLabel": "Letzte Seite",
|
||||||
|
"nextPageLabel": "Nächste Seite",
|
||||||
|
"previousPageLabel": "Vorherige Seite",
|
||||||
|
"rowsPerPageLabel": "Zeilen pro Seite",
|
||||||
|
"jumpToPageDropdownLabel": "Zum Dropdown-Menü springen",
|
||||||
|
"jumpToPageInputLabel": "Zum Eingabefeld springen",
|
||||||
|
"selectRow": "Zeile ausgewählt",
|
||||||
|
"unselectRow": "Zeile abgewählt",
|
||||||
|
"expandRow": "Zeile erweitert",
|
||||||
|
"collapseRow": "Zeile reduziert",
|
||||||
|
"showFilterMenu": "Filtermenü anzeigen",
|
||||||
|
"hideFilterMenu": "Filtermenü ausblenden",
|
||||||
|
"filterOperator": "Filteroperator",
|
||||||
|
"filterConstraint": "Filterbeschränkung",
|
||||||
|
"editRow": "Zeile bearbeiten",
|
||||||
|
"saveEdit": "Änderungen speichern",
|
||||||
|
"cancelEdit": "Änderungen abbrechen",
|
||||||
|
"listView": "Listenansicht",
|
||||||
|
"gridView": "Rasteransicht",
|
||||||
|
"slide": "Folie",
|
||||||
|
"slideNumber": "{slideNumber}",
|
||||||
|
"zoomImage": "Bild vergrößern",
|
||||||
|
"zoomIn": "Vergrößern",
|
||||||
|
"zoomOut": "Verkleinern",
|
||||||
|
"rotateRight": "Nach rechts drehen",
|
||||||
|
"rotateLeft": "Nach links drehen"
|
||||||
|
}
|
||||||
|
}
|
155
frontend/src/i18n/translations/primevue/prime_vue_local_en.json
Normal file
155
frontend/src/i18n/translations/primevue/prime_vue_local_en.json
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
{
|
||||||
|
"startsWith": "Starts with",
|
||||||
|
"contains": "Contains",
|
||||||
|
"notContains": "Not contains",
|
||||||
|
"endsWith": "Ends with",
|
||||||
|
"equals": "Equals",
|
||||||
|
"notEquals": "Not equals",
|
||||||
|
"noFilter": "No Filter",
|
||||||
|
"filter": "Filter",
|
||||||
|
"lt": "Less than",
|
||||||
|
"lte": "Less than or equal to",
|
||||||
|
"gt": "Greater than",
|
||||||
|
"gte": "Greater than or equal to",
|
||||||
|
"dateIs": "Date is",
|
||||||
|
"dateIsNot": "Date is not",
|
||||||
|
"dateBefore": "Date is before",
|
||||||
|
"dateAfter": "Date is after",
|
||||||
|
"custom": "Custom",
|
||||||
|
"clear": "Clear",
|
||||||
|
"apply": "Apply",
|
||||||
|
"matchAll": "Match All",
|
||||||
|
"matchAny": "Match Any",
|
||||||
|
"addRule": "Add Rule",
|
||||||
|
"removeRule": "Remove Rule",
|
||||||
|
"accept": "Yes",
|
||||||
|
"reject": "No",
|
||||||
|
"choose": "Choose",
|
||||||
|
"upload": "Upload",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"completed": "Completed",
|
||||||
|
"pending": "Pending",
|
||||||
|
"fileSizeTypes": ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
|
||||||
|
"dayNames": [
|
||||||
|
"Sunday",
|
||||||
|
"Monday",
|
||||||
|
"Tuesday",
|
||||||
|
"Wednesday",
|
||||||
|
"Thursday",
|
||||||
|
"Friday",
|
||||||
|
"Saturday"
|
||||||
|
],
|
||||||
|
"dayNamesShort": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
|
||||||
|
"dayNamesMin": ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"],
|
||||||
|
"monthNames": [
|
||||||
|
"January",
|
||||||
|
"February",
|
||||||
|
"March",
|
||||||
|
"April",
|
||||||
|
"May",
|
||||||
|
"June",
|
||||||
|
"July",
|
||||||
|
"August",
|
||||||
|
"September",
|
||||||
|
"October",
|
||||||
|
"November",
|
||||||
|
"December"
|
||||||
|
],
|
||||||
|
"monthNamesShort": [
|
||||||
|
"Jan",
|
||||||
|
"Feb",
|
||||||
|
"Mar",
|
||||||
|
"Apr",
|
||||||
|
"May",
|
||||||
|
"Jun",
|
||||||
|
"Jul",
|
||||||
|
"Aug",
|
||||||
|
"Sep",
|
||||||
|
"Oct",
|
||||||
|
"Nov",
|
||||||
|
"Dec"
|
||||||
|
],
|
||||||
|
"chooseYear": "Choose Year",
|
||||||
|
"chooseMonth": "Choose Month",
|
||||||
|
"chooseDate": "Choose Date",
|
||||||
|
"prevDecade": "Previous Decade",
|
||||||
|
"nextDecade": "Next Decade",
|
||||||
|
"prevYear": "Previous Year",
|
||||||
|
"nextYear": "Next Year",
|
||||||
|
"prevMonth": "Previous Month",
|
||||||
|
"nextMonth": "Next Month",
|
||||||
|
"prevHour": "Previous Hour",
|
||||||
|
"nextHour": "Next Hour",
|
||||||
|
"prevMinute": "Previous Minute",
|
||||||
|
"nextMinute": "Next Minute",
|
||||||
|
"prevSecond": "Previous Second",
|
||||||
|
"nextSecond": "Next Second",
|
||||||
|
"am": "AM",
|
||||||
|
"pm": "PM",
|
||||||
|
"today": "Today",
|
||||||
|
"now": "Now",
|
||||||
|
"weekHeader": "Wk",
|
||||||
|
"firstDayOfWeek": 0,
|
||||||
|
"showMonthAfterYear": false,
|
||||||
|
"dateFormat": "mm/dd/yy",
|
||||||
|
"weak": "Weak",
|
||||||
|
"medium": "Medium",
|
||||||
|
"strong": "Strong",
|
||||||
|
"passwordPrompt": "Enter a password",
|
||||||
|
"emptyFilterMessage": "No results found",
|
||||||
|
"searchMessage": "{0} results are available",
|
||||||
|
"selectionMessage": "{0} items selected",
|
||||||
|
"emptySelectionMessage": "No selected item",
|
||||||
|
"emptySearchMessage": "No results found",
|
||||||
|
"emptyMessage": "No available options",
|
||||||
|
"aria": {
|
||||||
|
"trueLabel": "True",
|
||||||
|
"falseLabel": "False",
|
||||||
|
"nullLabel": "Not Selected",
|
||||||
|
"star": "1 star",
|
||||||
|
"stars": "{star} stars",
|
||||||
|
"selectAll": "All items selected",
|
||||||
|
"unselectAll": "All items unselected",
|
||||||
|
"close": "Close",
|
||||||
|
"previous": "Previous",
|
||||||
|
"next": "Next",
|
||||||
|
"navigation": "Navigation",
|
||||||
|
"scrollTop": "Scroll Top",
|
||||||
|
"moveTop": "Move Top",
|
||||||
|
"moveUp": "Move Up",
|
||||||
|
"moveDown": "Move Down",
|
||||||
|
"moveBottom": "Move Bottom",
|
||||||
|
"moveToTarget": "Move to Target",
|
||||||
|
"moveToSource": "Move to Source",
|
||||||
|
"moveAllToTarget": "Move All to Target",
|
||||||
|
"moveAllToSource": "Move All to Source",
|
||||||
|
"pageLabel": "Page {page}",
|
||||||
|
"firstPageLabel": "First Page",
|
||||||
|
"lastPageLabel": "Last Page",
|
||||||
|
"nextPageLabel": "Next Page",
|
||||||
|
"previousPageLabel": "Previous Page",
|
||||||
|
"rowsPerPageLabel": "Rows per page",
|
||||||
|
"jumpToPageDropdownLabel": "Jump to Page Dropdown",
|
||||||
|
"jumpToPageInputLabel": "Jump to Page Input",
|
||||||
|
"selectRow": "Row Selected",
|
||||||
|
"unselectRow": "Row Unselected",
|
||||||
|
"expandRow": "Row Expanded",
|
||||||
|
"collapseRow": "Row Collapsed",
|
||||||
|
"showFilterMenu": "Show Filter Menu",
|
||||||
|
"hideFilterMenu": "Hide Filter Menu",
|
||||||
|
"filterOperator": "Filter Operator",
|
||||||
|
"filterConstraint": "Filter Constraint",
|
||||||
|
"editRow": "Edit Row",
|
||||||
|
"saveEdit": "Save Edit",
|
||||||
|
"cancelEdit": "Cancel Edit",
|
||||||
|
"listView": "List View",
|
||||||
|
"gridView": "Grid View",
|
||||||
|
"slide": "Slide",
|
||||||
|
"slideNumber": "{slideNumber}",
|
||||||
|
"zoomImage": "Zoom Image",
|
||||||
|
"zoomIn": "Zoom In",
|
||||||
|
"zoomOut": "Zoom Out",
|
||||||
|
"rotateRight": "Rotate Right",
|
||||||
|
"rotateLeft": "Rotate Left"
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
import "source-sans-pro/source-sans-pro.css";
|
import "source-sans/source-sans-3.css";
|
||||||
|
|
||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
import "./style.css";
|
import "./style.css";
|
||||||
@@ -14,6 +14,7 @@ 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";
|
||||||
|
import Slider from "primevue/slider";
|
||||||
import ToggleButton from "primevue/togglebutton";
|
import ToggleButton from "primevue/togglebutton";
|
||||||
import "primeicons/primeicons.css";
|
import "primeicons/primeicons.css";
|
||||||
import "primeflex/primeflex.css";
|
import "primeflex/primeflex.css";
|
||||||
@@ -35,8 +36,8 @@ import DialogService from "primevue/dialogservice";
|
|||||||
import ProgressSpinner from "primevue/progressspinner";
|
import ProgressSpinner from "primevue/progressspinner";
|
||||||
import Checkbox from "primevue/checkbox";
|
import Checkbox from "primevue/checkbox";
|
||||||
import Skeleton from "primevue/skeleton";
|
import Skeleton from "primevue/skeleton";
|
||||||
|
import Calendar from "primevue/calendar";
|
||||||
import i18n from "./i18n";
|
import i18n from "./i18n";
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
|
|
||||||
@@ -57,6 +58,7 @@ app.component("InputText", InputText);
|
|||||||
app.component("InputSwitch", InputSwitch);
|
app.component("InputSwitch", InputSwitch);
|
||||||
app.component("Card", Card);
|
app.component("Card", Card);
|
||||||
app.component("DataView", DataView);
|
app.component("DataView", DataView);
|
||||||
|
app.component("Slider", Slider);
|
||||||
app.component("ToggleButton", ToggleButton);
|
app.component("ToggleButton", ToggleButton);
|
||||||
app.component("SpeedDial", SpeedDial);
|
app.component("SpeedDial", SpeedDial);
|
||||||
app.component("TabView", TabView);
|
app.component("TabView", TabView);
|
||||||
@@ -72,5 +74,6 @@ app.component("DynamicDialog", DynamicDialog);
|
|||||||
app.component("ProgressSpinner", ProgressSpinner);
|
app.component("ProgressSpinner", ProgressSpinner);
|
||||||
app.component("Checkbox", Checkbox);
|
app.component("Checkbox", Checkbox);
|
||||||
app.component("Skeleton", Skeleton);
|
app.component("Skeleton", Skeleton);
|
||||||
|
app.component("Calendar", Calendar);
|
||||||
|
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
|
@@ -12,6 +12,7 @@ const EditAdditionalModules = () =>
|
|||||||
import("../view/editCalendar/EditAdditionalModules.vue");
|
import("../view/editCalendar/EditAdditionalModules.vue");
|
||||||
const EditModules = () => import("../view/editCalendar/EditModules.vue");
|
const EditModules = () => import("../view/editCalendar/EditModules.vue");
|
||||||
const CourseSelection = () => import("../view/CourseSelection.vue");
|
const CourseSelection = () => import("../view/CourseSelection.vue");
|
||||||
|
const FreeRooms = () => import("../view/FreeRooms.vue");
|
||||||
|
|
||||||
import i18n from "../i18n";
|
import i18n from "../i18n";
|
||||||
|
|
||||||
@@ -24,10 +25,15 @@ const router = createRouter({
|
|||||||
component: CourseSelection,
|
component: CourseSelection,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/rooms",
|
path: "/rooms/occupancy",
|
||||||
name: "room-finder",
|
name: "room-schedule",
|
||||||
component: RoomFinder,
|
component: RoomFinder,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/rooms/free",
|
||||||
|
name: "free-rooms",
|
||||||
|
component: FreeRooms,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/faq",
|
path: "/faq",
|
||||||
name: "faq",
|
name: "faq",
|
||||||
|
251
frontend/src/view/FreeRooms.vue
Normal file
251
frontend/src/view/FreeRooms.vue
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
<template>
|
||||||
|
<DynamicPage
|
||||||
|
:hide-content="availableRooms.length === 0"
|
||||||
|
:headline="$t('freeRooms.freeRooms')"
|
||||||
|
:sub-title="$t('freeRooms.detail')"
|
||||||
|
icon="pi pi-search"
|
||||||
|
:button="{
|
||||||
|
label: $t('freeRooms.search'),
|
||||||
|
icon: 'pi pi-search',
|
||||||
|
disabled: isLater,
|
||||||
|
onClick: loadFreeRooms,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #selection="{ flexSpecs }">
|
||||||
|
<Calendar
|
||||||
|
v-model="date"
|
||||||
|
:class="flexSpecs"
|
||||||
|
:placeholder="$t('freeRooms.pleaseSelectDate')"
|
||||||
|
:empty-message="$t('roomFinderPage.noRoomsAvailable')"
|
||||||
|
date-format="dd.mm.yy"
|
||||||
|
panel-class="min-w-min"
|
||||||
|
touch-u-i
|
||||||
|
/>
|
||||||
|
<div class="break" />
|
||||||
|
<Calendar
|
||||||
|
v-if="mobilePage"
|
||||||
|
v-model="start"
|
||||||
|
type="time"
|
||||||
|
placeholder="start"
|
||||||
|
time-only
|
||||||
|
hour-format="24"
|
||||||
|
date-format="HH:mm"
|
||||||
|
:class="[{ 'p-invalid': isLater }, flexSpecs]"
|
||||||
|
panel-class="min-w-min"
|
||||||
|
touch-u-i
|
||||||
|
@update:model-value="
|
||||||
|
() => {
|
||||||
|
timeRange[0] = start.getHours() * 60 + start.getMinutes();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<Calendar
|
||||||
|
v-if="mobilePage"
|
||||||
|
v-model="end"
|
||||||
|
type="time"
|
||||||
|
time-only
|
||||||
|
hour-format="24"
|
||||||
|
placeholder="end"
|
||||||
|
date-format="HH:mm"
|
||||||
|
:class="[{ 'p-invalid': isLater }, flexSpecs]"
|
||||||
|
panel-class="min-w-min"
|
||||||
|
touch-u-i
|
||||||
|
@update:model-value="
|
||||||
|
() => {
|
||||||
|
timeRange[1] = end.getHours() * 60 + end.getMinutes();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="!mobilePage"
|
||||||
|
class="flex-grow-1 relative mb-2"
|
||||||
|
:class="flexSpecs"
|
||||||
|
>
|
||||||
|
<Tag
|
||||||
|
:value="formatTime(start)"
|
||||||
|
class="opacity-0 pointer-events-none text-xl lg:text-base"
|
||||||
|
/>
|
||||||
|
<Tag
|
||||||
|
:value="formatTime(start)"
|
||||||
|
class="absolute time-tag text-xl lg:text-base"
|
||||||
|
:style="{ left: timeToPercentString(timeRange[0]) }"
|
||||||
|
:class="startMoved.value ? 'moved' : ''"
|
||||||
|
/>
|
||||||
|
<Tag
|
||||||
|
:value="formatTime(end)"
|
||||||
|
class="absolute time-tag text-xl lg:text-base"
|
||||||
|
:style="{ left: timeToPercentString(timeRange[1]) }"
|
||||||
|
:class="endMoved.value ? 'moved' : ''"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="break" />
|
||||||
|
<Slider
|
||||||
|
v-if="!mobilePage"
|
||||||
|
v-model="timeRange"
|
||||||
|
:class="flexSpecs"
|
||||||
|
range
|
||||||
|
:min="0"
|
||||||
|
:max="24 * 60"
|
||||||
|
:step="5"
|
||||||
|
@change="updateTimeRange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<DataTable
|
||||||
|
v-model:filters="filters"
|
||||||
|
:value="availableRooms"
|
||||||
|
data-key="id"
|
||||||
|
filter-display="row"
|
||||||
|
paginator
|
||||||
|
:rows="10"
|
||||||
|
:global-filter-fields="['room']"
|
||||||
|
>
|
||||||
|
<Column field="room" sortable :header="$t('freeRooms.room')">
|
||||||
|
<template #filter="{ filterModel, filterCallback }">
|
||||||
|
<InputText
|
||||||
|
v-model="filterModel.value"
|
||||||
|
type="text"
|
||||||
|
class="p-column-filter"
|
||||||
|
:placeholder="$t('freeRooms.searchByRoom')"
|
||||||
|
@input="filterCallback()"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
</DataTable>
|
||||||
|
</template>
|
||||||
|
</DynamicPage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, inject, Ref, ref } from "vue";
|
||||||
|
import DynamicPage from "@/view/DynamicPage.vue";
|
||||||
|
import { requestFreeRooms } from "@/api/requestFreeRooms.ts";
|
||||||
|
import { FilterMatchMode } from "primevue/api";
|
||||||
|
import { padStart } from "@fullcalendar/core/internal";
|
||||||
|
|
||||||
|
const mobilePage = inject("mobilePage") as Ref<boolean>;
|
||||||
|
const filters = ref({
|
||||||
|
room: { value: null, matchMode: FilterMatchMode.CONTAINS, label: "Room" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const date: Ref<Date> = ref(new Date(Date.now()));
|
||||||
|
const start: Ref<Date> = ref(new Date(0));
|
||||||
|
const end: Ref<Date> = ref(new Date(0));
|
||||||
|
|
||||||
|
class Moved {
|
||||||
|
value: boolean = false;
|
||||||
|
timeout: NodeJS.Timeout | null = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startMoved: Ref<Moved> = ref(new Moved());
|
||||||
|
const endMoved: Ref<Moved> = ref(new Moved());
|
||||||
|
|
||||||
|
start.value.setHours(new Date().getHours());
|
||||||
|
end.value.setHours(Math.min(new Date().getHours() + 3, 23));
|
||||||
|
if (end.value.getHours() === 23) {
|
||||||
|
start.value.setHours(22);
|
||||||
|
end.value.setMinutes(55);
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeRange: Ref<number[]> = ref([
|
||||||
|
start.value.getHours() * 60 + start.value.getMinutes(),
|
||||||
|
end.value.getHours() * 60 + end.value.getMinutes(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
function buttonMovedTimeout(
|
||||||
|
time: number,
|
||||||
|
timeDate: Date,
|
||||||
|
moved: Ref<Moved>,
|
||||||
|
): void {
|
||||||
|
if (time !== timeDate.getHours() * 60 + timeDate.getMinutes()) {
|
||||||
|
if (moved.value.timeout !== null) {
|
||||||
|
clearTimeout(moved.value.timeout);
|
||||||
|
}
|
||||||
|
moved.value.value = true;
|
||||||
|
moved.value.timeout = setTimeout(() => {
|
||||||
|
moved.value.value = false;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTimeRange(): void {
|
||||||
|
buttonMovedTimeout(timeRange.value[0], start.value, startMoved);
|
||||||
|
buttonMovedTimeout(timeRange.value[1], end.value, endMoved);
|
||||||
|
|
||||||
|
if (timeRange.value[0] > timeRange.value[1]) {
|
||||||
|
timeRange.value[1] = timeRange.value[0];
|
||||||
|
}
|
||||||
|
start.value.setHours(Math.floor(timeRange.value[0] / 60));
|
||||||
|
start.value.setMinutes(timeRange.value[0] % 60);
|
||||||
|
end.value.setHours(Math.floor(timeRange.value[1] / 60));
|
||||||
|
end.value.setMinutes(timeRange.value[1] % 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(time: Date): string {
|
||||||
|
return (
|
||||||
|
padStart(time.getHours().toString(), 2) +
|
||||||
|
":" +
|
||||||
|
padStart(time.getMinutes().toString(), 2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function timeToPercentString(time: number): string {
|
||||||
|
return `${(Number(time) * 100) / (24 * 60)}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadFreeRooms(): Promise<void> {
|
||||||
|
availableRooms.value = [];
|
||||||
|
const startDate = new Date(date.value.getTime());
|
||||||
|
startDate.setHours(start.value.getHours());
|
||||||
|
startDate.setMinutes(start.value.getMinutes());
|
||||||
|
const endDate = new Date(date.value.getTime());
|
||||||
|
endDate.setHours(end.value.getHours());
|
||||||
|
endDate.setMinutes(end.value.getMinutes());
|
||||||
|
await requestFreeRooms(startDate.toISOString(), endDate.toISOString()).then(
|
||||||
|
(data) => {
|
||||||
|
availableRooms.value = data.map((room, index) => {
|
||||||
|
return { id: index, room: room };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLater = computed(() => {
|
||||||
|
return start.value > end.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const availableRooms: Ref<{ id: number; room: string }[]> = ref([]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.break {
|
||||||
|
flex-basis: 100%;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-tag {
|
||||||
|
transform: translateX(-50%);
|
||||||
|
border: 2px solid var(--primary-color);
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: opacity 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-tag.moved {
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-tag::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
bottom: -0.8rem;
|
||||||
|
left: 50%;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0.8rem 0.5rem 0 0.5rem;
|
||||||
|
border-color: var(--primary-color) transparent transparent transparent;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
</style>
|
@@ -3,20 +3,16 @@ import { Ref, ref } from "vue";
|
|||||||
import { fetchRoom } from "../api/fetchRoom.ts";
|
import { fetchRoom } from "../api/fetchRoom.ts";
|
||||||
import DynamicPage from "./DynamicPage.vue";
|
import DynamicPage from "./DynamicPage.vue";
|
||||||
import RoomOccupation from "../components/RoomOccupation.vue";
|
import RoomOccupation from "../components/RoomOccupation.vue";
|
||||||
|
import { computedAsync } from "@vueuse/core";
|
||||||
|
|
||||||
const rooms = async () => {
|
const roomsList = computedAsync(async () => {
|
||||||
return await fetchRoom();
|
return await fetchRoom().then((data) =>
|
||||||
};
|
data.map((room) => {
|
||||||
|
|
||||||
const roomsList: Ref<{ name: string }[]> = ref([]);
|
|
||||||
const selectedRoom: Ref<{ name: string }> = ref({ name: "" });
|
|
||||||
|
|
||||||
rooms().then(
|
|
||||||
(data) =>
|
|
||||||
(roomsList.value = data.map((room) => {
|
|
||||||
return { name: room };
|
return { name: room };
|
||||||
})),
|
}),
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
const selectedRoom: Ref<{ name: string }> = ref({ name: "" });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@@ -16,5 +16,12 @@ export default defineConfig({
|
|||||||
watch: {
|
watch: {
|
||||||
usePolling: true,
|
usePolling: true,
|
||||||
},
|
},
|
||||||
|
proxy: {
|
||||||
|
"/api": {
|
||||||
|
target: "http://localhost:8090/api",
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, ""),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user