diff --git a/backend/database.go b/backend/database.go index 42d1229..9e123a1 100644 --- a/backend/database.go +++ b/backend/database.go @@ -107,8 +107,10 @@ func createTables(db *sql.DB) { title TEXT NOT NULL, notes TEXT, taken_by_user_id INTEGER, + schedule_id INTEGER NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY(taken_by_user_id) REFERENCES users(id) + FOREIGN KEY(taken_by_user_id) REFERENCES users(id), + FOREIGN KEY(schedule_id) REFERENCES schedules(id) )`, } @@ -637,32 +639,53 @@ func DeleteNonManualTimeEntriesByUserAndWeek(db *sql.DB, userID int, year int, w for day := 0; day <= 4; day++ { dateList = append(dateList, dates.Dates[fmt.Sprint(day)]) } + tx, err := db.Begin() + if err != nil { + return err + } + defer tx.Rollback() + + _, err = tx.Exec(` + UPDATE substitutions + SET taken_by_user_id = NULL + WHERE taken_by_user_id = ? + AND date IN (?, ?, ?, ?, ?) + `, userID, dateList[0], dateList[1], dateList[2], dateList[3], dateList[4]) + if err != nil { + return err + } query := `DELETE FROM time_entries WHERE user_id = ? AND type != 'manual' AND date IN (?, ?, ?, ?, ?)` - _, err := db.Exec(query, userID, dateList[0], dateList[1], dateList[2], dateList[3], dateList[4]) - return err + _, err = tx.Exec(query, userID, dateList[0], dateList[1], dateList[2], dateList[3], dateList[4]) + if err != nil { + return err + } + + return tx.Commit() } -func CreateSubstitution(db *sql.DB, date, start, end, title, notes string) error { +func CreateSubstitution(db *sql.DB, date, start, end, title, notes string, scheduleID int) error { _, err := db.Exec(` - INSERT INTO substitutions (date, start_time, end_time, title, notes) - VALUES (?, ?, ?, ?, ?) - `, date, start, end, title, notes) + INSERT INTO substitutions (date, start_time, end_time, title, notes, schedule_id) + VALUES (?, ?, ?, ?, ?, ?) + `, date, start, end, title, notes, scheduleID) return err } func GetOpenSubstitutions(db *sql.DB) ([]Substitution, error) { + today := time.Now().Format("2006-01-02") + rows, err := db.Query(` - SELECT id, date, start_time, end_time, title, notes, created_at + SELECT id, date, start_time, end_time, title, notes, schedule_id, created_at FROM substitutions WHERE taken_by_user_id IS NULL - AND date >= date('now') + AND date >= ? ORDER BY date ASC, start_time ASC - `) + `, today) if err != nil { return nil, err } @@ -671,7 +694,7 @@ func GetOpenSubstitutions(db *sql.DB) ([]Substitution, error) { var subs []Substitution for rows.Next() { var s Substitution - if err := rows.Scan(&s.ID, &s.Date, &s.StartTime, &s.EndTime, &s.Title, &s.Notes, &s.CreatedAt); err != nil { + if err := rows.Scan(&s.ID, &s.Date, &s.StartTime, &s.EndTime, &s.Title, &s.Notes, &s.ScheduleID, &s.CreatedAt); err != nil { continue } subs = append(subs, s) @@ -679,50 +702,19 @@ func GetOpenSubstitutions(db *sql.DB) ([]Substitution, error) { return subs, nil } -func AcceptSubstitution(db *sql.DB, substitutionID int, userID int) error { - tx, err := db.Begin() - if err != nil { - return err - } - defer tx.Rollback() - - var currentDate, start, end, title string - err = tx.QueryRow(` - SELECT date, start_time, end_time, title - FROM substitutions - WHERE id = ? AND taken_by_user_id IS NULL - `, substitutionID).Scan(¤tDate, &start, &end, &title) - - if err == sql.ErrNoRows { - return fmt.Errorf("Vertretung wurde bereits vergeben oder existiert nicht") - } - if err != nil { - return err - } - - _, err = tx.Exec(` - UPDATE substitutions - SET taken_by_user_id = ? - WHERE id = ? - `, userID, substitutionID) - if err != nil { - return err - } - - _, err = tx.Exec(` - INSERT INTO time_entries (user_id, schedule_id, date, type, start_time, end_time) - VALUES (?, 0, ?, 'lesson', ?, ?) - `, userID, currentDate, start, end) - if err != nil { - return err - } - - return tx.Commit() -} - func GetAllSubstitutions(db *sql.DB) ([]Substitution, error) { rows, err := db.Query(` - SELECT s.id, s.date, s.start_time, s.end_time, s.title, s.notes, s.taken_by_user_id, u.username + SELECT + s.id, + s.date, + s.start_time, + s.end_time, + s.title, + s.notes, + s.schedule_id, + s.created_at, + s.taken_by_user_id, + u.username FROM substitutions s LEFT JOIN users u ON s.taken_by_user_id = u.id ORDER BY s.date DESC @@ -735,18 +727,116 @@ func GetAllSubstitutions(db *sql.DB) ([]Substitution, error) { var subs []Substitution for rows.Next() { var s Substitution + var takenID sql.NullInt64 var takenName sql.NullString - if err := rows.Scan(&s.ID, &s.Date, &s.StartTime, &s.EndTime, &s.Title, &s.Notes, &takenID, &takenName); err != nil { + if err := rows.Scan( + &s.ID, + &s.Date, + &s.StartTime, + &s.EndTime, + &s.Title, + &s.Notes, + &s.ScheduleID, + &s.CreatedAt, + &takenID, + &takenName, + ); err != nil { continue } + if takenID.Valid { id := int(takenID.Int64) s.TakenByUserID = &id s.TakenByUsername = takenName.String } + subs = append(subs, s) } return subs, nil } + +func DeleteSubstitution(db *sql.DB, id int) error { + tx, err := db.Begin() + if err != nil { + return err + } + defer tx.Rollback() + + var takenByUserID sql.NullInt64 + var scheduleID int + var date string + + err = tx.QueryRow(` + SELECT taken_by_user_id, schedule_id, date + FROM substitutions + WHERE id = ? + `, id).Scan(&takenByUserID, &scheduleID, &date) + if err != nil { + if err == sql.ErrNoRows { + return nil + } + return err + } + + if takenByUserID.Valid { + userID := int(takenByUserID.Int64) + + _, err = tx.Exec(` + DELETE FROM time_entries + WHERE user_id = ? AND schedule_id = ? AND date = ? + `, userID, scheduleID, date) + if err != nil { + return err + } + } + + _, err = tx.Exec("DELETE FROM substitutions WHERE id = ?", id) + if err != nil { + return err + } + + return tx.Commit() +} + +func AcceptSubstitution(db *sql.DB, substitutionID int, userID int) error { + tx, err := db.Begin() + if err != nil { + return err + } + defer tx.Rollback() + + var currentDate, start, end string + var scheduleID int + var scheduleType string + + err = tx.QueryRow(` + SELECT s.date, s.start_time, s.end_time, s.schedule_id, sch.type + FROM substitutions s + JOIN schedules sch ON s.schedule_id = sch.id + WHERE s.id = ? AND s.taken_by_user_id IS NULL + `, substitutionID).Scan(¤tDate, &start, &end, &scheduleID, &scheduleType) + + if err == sql.ErrNoRows { + return fmt.Errorf("Vertretung wurde bereits vergeben oder existiert nicht") + } + if err != nil { + return err + } + + _, err = tx.Exec(`UPDATE substitutions SET taken_by_user_id = ? WHERE id = ?`, userID, substitutionID) + if err != nil { + return err + } + + _, err = tx.Exec(` + INSERT INTO time_entries (user_id, schedule_id, date, type, start_time, end_time) + VALUES (?, ?, ?, ?, ?, ?) + `, userID, scheduleID, currentDate, scheduleType, start, end) + if err != nil { + return err + } + + return tx.Commit() +} diff --git a/backend/handlers.go b/backend/handlers.go index 31c2b97..1d63a92 100644 --- a/backend/handlers.go +++ b/backend/handlers.go @@ -854,22 +854,6 @@ func (app *App) UploadLicenseHandler(c echo.Context) error { return c.JSON(http.StatusOK, map[string]string{"message": "Lizenz erfolgreich aktiviert"}) } -func (app *App) CreateSubstitutionHandler(c echo.Context) error { - var req CreateSubstitutionRequest - if err := c.Bind(&req); err != nil { - return HandleError(c, ErrInvalidInputMsg("Eingabedaten")) - } - if req.Date == "" || req.StartTime == "" || req.Title == "" { - return HandleError(c, ErrMissingFieldMsg("Pflichtfelder")) - } - - if err := CreateSubstitution(app.DB, req.Date, req.StartTime, req.EndTime, req.Title, req.Notes); err != nil { - return HandleError(c, ErrDatabaseMsg(err)) - } - - return c.JSON(http.StatusCreated, map[string]string{"message": "Vertretung ausgeschrieben"}) -} - func (app *App) GetAllSubstitutionsHandler(c echo.Context) error { subs, err := GetAllSubstitutions(app.DB) if err != nil { @@ -912,3 +896,25 @@ func (app *App) AcceptSubstitutionHandler(c echo.Context) error { return c.JSON(http.StatusOK, map[string]string{"message": "Vertretung erfolgreich übernommen!"}) } + +func (app *App) CreateSubstitutionHandler(c echo.Context) error { + var req CreateSubstitutionRequest + if err := c.Bind(&req); err != nil { + return HandleError(c, ErrInvalidInputMsg("Eingabedaten")) + } + if err := CreateSubstitution(app.DB, req.Date, req.StartTime, req.EndTime, req.Title, req.Notes, req.ScheduleID); err != nil { + return HandleError(c, ErrDatabaseMsg(err)) + } + return c.JSON(http.StatusCreated, map[string]string{"message": "Vertretung ausgeschrieben"}) +} + +func (app *App) DeleteSubstitutionHandler(c echo.Context) error { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + return HandleError(c, ErrInvalidInputMsg("ID")) + } + if err := DeleteSubstitution(app.DB, id); err != nil { + return HandleError(c, ErrDatabaseMsg(err)) + } + return c.NoContent(http.StatusOK) +} diff --git a/backend/main.go b/backend/main.go index 0cc162e..fd9e98d 100644 --- a/backend/main.go +++ b/backend/main.go @@ -100,6 +100,7 @@ func main() { admin.POST("/settings/license", app.UploadLicenseHandler) admin.GET("/substitutions", app.GetAllSubstitutionsHandler) admin.POST("/substitutions", app.CreateSubstitutionHandler) + admin.DELETE("/substitutions/:id", app.DeleteSubstitutionHandler) } distDir, err := fs.Sub(frontendDist, "dist") diff --git a/backend/models.go b/backend/models.go index 4e2f2e8..9fc13fb 100644 --- a/backend/models.go +++ b/backend/models.go @@ -134,15 +134,17 @@ type Substitution struct { EndTime string `json:"end_time"` Title string `json:"title"` Notes string `json:"notes"` + ScheduleID int `json:"schedule_id"` TakenByUserID *int `json:"taken_by_user_id,omitempty"` TakenByUsername string `json:"taken_by_username,omitempty"` CreatedAt time.Time `json:"created_at"` } type CreateSubstitutionRequest struct { - Date string `json:"date" validate:"required"` - StartTime string `json:"start_time" validate:"required"` - EndTime string `json:"end_time" validate:"required"` - Title string `json:"title" validate:"required"` - Notes string `json:"notes"` + Date string `json:"date" validate:"required"` + StartTime string `json:"start_time" validate:"required"` + EndTime string `json:"end_time" validate:"required"` + Title string `json:"title" validate:"required"` + Notes string `json:"notes"` + ScheduleID int `json:"schedule_id"` } diff --git a/frontend/src/components/UserDashboard.svelte b/frontend/src/components/UserDashboard.svelte index 4c7fca2..beef746 100644 --- a/frontend/src/components/UserDashboard.svelte +++ b/frontend/src/components/UserDashboard.svelte @@ -38,7 +38,6 @@ let showPwModal = false; let pwData = { old: "", new1: "", new2: "" }; - // State für die Ansicht let activeView = "schedule"; let openSubstitutions = []; diff --git a/frontend/src/components/admin/AdminSubstitutionsTab.svelte b/frontend/src/components/admin/AdminSubstitutionsTab.svelte index 086647a..3aae90e 100644 --- a/frontend/src/components/admin/AdminSubstitutionsTab.svelte +++ b/frontend/src/components/admin/AdminSubstitutionsTab.svelte @@ -1,144 +1,417 @@ -
+ Klicken Sie auf eine Stunde, um eine Vertretung auszuschreiben. + Bereits ausgeschriebene Vertretungen werden farbig markiert. +
+ +|
+ {day.name}
+
+ {day.date}
+
+ |
+ {/each}
+
|---|
|
+
+ {#each schedules.filter((s) => s.dayOfWeek === day.dayIndex) as schedule}
+ {@const sub = findSubstitution(
+ day.date,
+ schedule.startTime,
+ )}
+
+
+
+ openCreateModal(
+ schedule,
+ day.date,
+ )}
+ class="relative cursor-pointer hover:scale-[1.02] transition-transform group"
+ >
+
+ {/each}
+
+ {#if schedules.filter((s) => s.dayOfWeek === day.dayIndex).length === 0}
+
+ {#if sub.taken_by_user_id}
+
+ {/if}
+
+ {#if !sub}
+
+ Übernommen
+
+
+ {sub.taken_by_username}
+
+ {:else}
+
+ Gesucht
+
+
+ Offen
+
+ {/if}
+
+
+ {sub.title}
+
+
+ + Erstellen
+
+ {/if}
+
+ - Frei -
+
+ {/if}
+ |
+ {/each}
+
| Datum | -Zeit | -Titel | -Status | -
|---|---|---|---|
| {s.date} | -{s.start_time} - {s.end_time} | -
- {s.title}
- {#if s.notes}
- {s.notes}
- {/if}
- |
-
- {#if s.taken_by_user_id}
- Übernommen
-
- von {s.taken_by_username}
-
- {:else}
- Offen
- {/if}
- |
-
| Keine Einträge | |||
| Datum | +Zeit | +Titel / Notiz | +Status | ++ |
|---|---|---|---|---|
| {s.date} | +{s.start_time} - {s.end_time} | +
+ {s.title}
+ {#if s.notes}
+ {s.notes}
+ {/if}
+ |
+ + {#if s.taken_by_user_id} + ✓ {s.taken_by_username} + {:else} + Offen + {/if} + | ++ + | +
| Keine Einträge | ||||