started work on task assignment table
This commit is contained in:
@@ -1,36 +0,0 @@
|
|||||||
package availabilites
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/johannesbuehl/golunteer/backend/pkg/db"
|
|
||||||
"github.com/johannesbuehl/golunteer/backend/pkg/db/users"
|
|
||||||
)
|
|
||||||
|
|
||||||
type eventAvailabilites struct {
|
|
||||||
userName string `db:"userName"`
|
|
||||||
AvailabilityID int `db:"availabilityID"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Event(eventID int) (map[string]string, error) {
|
|
||||||
// get the availabilites for the event
|
|
||||||
var availabilitesRows []eventAvailabilites
|
|
||||||
|
|
||||||
if err := db.DB.Select(&availabilitesRows, "SELECT (userID, availabilityID) FROM USER_AVAILABILITES WHERE eventID = ?", eventID); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
// transform the result into a map
|
|
||||||
eventAvailabilities := map[string]string{}
|
|
||||||
|
|
||||||
// get the availabilites
|
|
||||||
if availabilitesMap, err := Keys(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if usersMap, err := users.Get(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
for _, a := range availabilitesRows {
|
|
||||||
eventAvailabilities[usersMap[a.userName].Name] = availabilitesMap[a.AvailabilityID].Text
|
|
||||||
}
|
|
||||||
|
|
||||||
return eventAvailabilities, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package availabilites
|
package availabilities
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/johannesbuehl/golunteer/backend/pkg/db"
|
"github.com/johannesbuehl/golunteer/backend/pkg/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
type availabilitesDB struct {
|
type availabilitiesDB struct {
|
||||||
Id int `db:"id"`
|
Id int `db:"id"`
|
||||||
Text string `db:"text"`
|
Text string `db:"text"`
|
||||||
Disabled bool `db:"disabled"`
|
Disabled bool `db:"disabled"`
|
||||||
@@ -22,31 +22,31 @@ type Availability struct {
|
|||||||
var c *cache.Cache
|
var c *cache.Cache
|
||||||
|
|
||||||
func Keys() (map[int]Availability, error) {
|
func Keys() (map[int]Availability, error) {
|
||||||
if availabilities, hit := c.Get("availabilites"); !hit {
|
if availabilities, hit := c.Get("availabilities"); !hit {
|
||||||
refresh()
|
refresh()
|
||||||
|
|
||||||
return nil, fmt.Errorf("availabilites not stored cached")
|
return nil, fmt.Errorf("availabilities not stored cached")
|
||||||
} else {
|
} else {
|
||||||
return availabilities.(map[int]Availability), nil
|
return availabilities.(map[int]Availability), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func refresh() {
|
func refresh() {
|
||||||
// get the availabilitesRaw from the database
|
// get the availabilitiesRaw from the database
|
||||||
var availabilitesRaw []availabilitesDB
|
var availabilitiesRaw []availabilitiesDB
|
||||||
|
|
||||||
if err := db.DB.Select(&availabilitesRaw, "SELECT * FROM AVAILABILITIES"); err == nil {
|
if err := db.DB.Select(&availabilitiesRaw, "SELECT * FROM AVAILABILITIES"); err == nil {
|
||||||
// convert the result in a map
|
// convert the result in a map
|
||||||
availabilites := map[int]Availability{}
|
availabilities := map[int]Availability{}
|
||||||
|
|
||||||
for _, a := range availabilitesRaw {
|
for _, a := range availabilitiesRaw {
|
||||||
availabilites[a.Id] = Availability{
|
availabilities[a.Id] = Availability{
|
||||||
Text: a.Text,
|
Text: a.Text,
|
||||||
Disabled: a.Disabled,
|
Disabled: a.Disabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Set("availabilites", availabilites)
|
c.Set("availabilities", availabilities)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
36
backend/pkg/db/availabilities/userAvailabilities.go
Normal file
36
backend/pkg/db/availabilities/userAvailabilities.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package availabilities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/johannesbuehl/golunteer/backend/pkg/db"
|
||||||
|
"github.com/johannesbuehl/golunteer/backend/pkg/db/users"
|
||||||
|
)
|
||||||
|
|
||||||
|
type eventAvailabilities struct {
|
||||||
|
UserName string `db:"userName"`
|
||||||
|
AvailabilityID int `db:"availabilityID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Event(eventID int) (map[string]string, error) {
|
||||||
|
// get the availabilities for the event
|
||||||
|
var availabilitiesRows []eventAvailabilities
|
||||||
|
|
||||||
|
if err := db.DB.Select(&availabilitiesRows, "SELECT userName, availabilityID FROM USER_AVAILABILITIES WHERE eventID = ?", eventID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
// transform the result into a map
|
||||||
|
eventAvailabilities := map[string]string{}
|
||||||
|
|
||||||
|
// get the availabilities
|
||||||
|
if availabilitiesMap, err := Keys(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if usersMap, err := users.Get(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
for _, a := range availabilitiesRows {
|
||||||
|
eventAvailabilities[usersMap[a.UserName].Name] = availabilitiesMap[a.AvailabilityID].Text
|
||||||
|
}
|
||||||
|
|
||||||
|
return eventAvailabilities, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package events
|
|||||||
import (
|
import (
|
||||||
"github.com/johannesbuehl/golunteer/backend/pkg/db"
|
"github.com/johannesbuehl/golunteer/backend/pkg/db"
|
||||||
"github.com/johannesbuehl/golunteer/backend/pkg/db/assignments"
|
"github.com/johannesbuehl/golunteer/backend/pkg/db/assignments"
|
||||||
|
"github.com/johannesbuehl/golunteer/backend/pkg/db/availabilities"
|
||||||
"github.com/johannesbuehl/golunteer/backend/pkg/logger"
|
"github.com/johannesbuehl/golunteer/backend/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,6 +12,11 @@ type EventWithAssignment struct {
|
|||||||
Tasks map[string]*string `json:"tasks"`
|
Tasks map[string]*string `json:"tasks"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EventWithAvailabilities struct {
|
||||||
|
EventWithAssignment
|
||||||
|
Availabilities map[string]string `json:"availabilities"`
|
||||||
|
}
|
||||||
|
|
||||||
type eventDataDB struct {
|
type eventDataDB struct {
|
||||||
Id int `db:"id" json:"id"`
|
Id int `db:"id" json:"id"`
|
||||||
Date string `db:"date" json:"date" validate:"required"`
|
Date string `db:"date" json:"date" validate:"required"`
|
||||||
@@ -18,18 +24,34 @@ type eventDataDB struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// transform the database-entry to an Event
|
// transform the database-entry to an Event
|
||||||
func (e *eventDataDB) Event() (EventWithAssignment, error) {
|
func (e eventDataDB) Event() (EventWithAssignment, error) {
|
||||||
// get the availabilites associated with the event
|
// get the assignments associated with the event
|
||||||
if assignemnts, err := assignments.Event(e.Id); err != nil {
|
if assignemnts, err := assignments.Event(e.Id); err != nil {
|
||||||
return EventWithAssignment{}, err
|
return EventWithAssignment{}, err
|
||||||
} else {
|
} else {
|
||||||
return EventWithAssignment{
|
return EventWithAssignment{
|
||||||
eventDataDB: *e,
|
eventDataDB: e,
|
||||||
Tasks: assignemnts,
|
Tasks: assignemnts,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e eventDataDB) EventWithAvailabilities() (EventWithAvailabilities, error) {
|
||||||
|
// get the event with assignments
|
||||||
|
if event, err := e.Event(); err != nil {
|
||||||
|
return EventWithAvailabilities{}, err
|
||||||
|
|
||||||
|
// get the availabilities
|
||||||
|
} else if availabilities, err := availabilities.Event(e.Id); err != nil {
|
||||||
|
return EventWithAvailabilities{}, err
|
||||||
|
} else {
|
||||||
|
return EventWithAvailabilities{
|
||||||
|
EventWithAssignment: event,
|
||||||
|
Availabilities: availabilities,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type EventCreate struct {
|
type EventCreate struct {
|
||||||
eventDataDB
|
eventDataDB
|
||||||
Tasks []int `json:"tasks" validate:"required,min=1"`
|
Tasks []int `json:"tasks" validate:"required,min=1"`
|
||||||
@@ -98,6 +120,25 @@ func WithAssignments() ([]EventWithAssignment, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithAvailabilities() ([]EventWithAvailabilities, error) {
|
||||||
|
// get all events
|
||||||
|
if eventsDB, err := All(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
events := make([]EventWithAvailabilities, len(eventsDB))
|
||||||
|
|
||||||
|
for ii, e := range eventsDB {
|
||||||
|
if ev, err := e.EventWithAvailabilities(); err != nil {
|
||||||
|
logger.Logger.Error().Msgf("can't get availabilities for event with id = %d: %v", e.Id, err)
|
||||||
|
} else {
|
||||||
|
events[ii] = ev
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return events, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func UserPending(userName string) (int, error) {
|
func UserPending(userName string) (int, error) {
|
||||||
var result struct {
|
var result struct {
|
||||||
Count int `db:"count(*)"`
|
Count int `db:"count(*)"`
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Name string `db:"text"`
|
Name string `db:"name"`
|
||||||
Password []byte `db:"password"`
|
Password []byte `db:"password"`
|
||||||
TokenID string `db:"tokenID"`
|
TokenID string `db:"tokenID"`
|
||||||
Admin bool `db:"disabled"`
|
Admin bool `db:"admin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var c *cache.Cache
|
var c *cache.Cache
|
||||||
@@ -21,7 +21,7 @@ func Get() (map[string]User, error) {
|
|||||||
if users, hit := c.Get("users"); !hit {
|
if users, hit := c.Get("users"); !hit {
|
||||||
refresh()
|
refresh()
|
||||||
|
|
||||||
return nil, fmt.Errorf("users not stored cached")
|
return nil, fmt.Errorf("users not cached")
|
||||||
} else {
|
} else {
|
||||||
return users.(map[string]User), nil
|
return users.(map[string]User), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,25 @@ func getEventsAssignments(args HandlerArgs) responseMessage {
|
|||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getEventsAvailabilities(args HandlerArgs) responseMessage {
|
||||||
|
response := responseMessage{}
|
||||||
|
|
||||||
|
// check for admin
|
||||||
|
if !args.User.Admin {
|
||||||
|
response.Status = fiber.StatusForbidden
|
||||||
|
} else {
|
||||||
|
if events, err := events.WithAvailabilities(); err != nil {
|
||||||
|
response.Status = fiber.StatusInternalServerError
|
||||||
|
|
||||||
|
logger.Error().Msgf("can't retrieve events with availabilities: %v", err)
|
||||||
|
} else {
|
||||||
|
response.Data = events
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
func getEventsUserPending(args HandlerArgs) responseMessage {
|
func getEventsUserPending(args HandlerArgs) responseMessage {
|
||||||
response := responseMessage{}
|
response := responseMessage{}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func handleLogin(c *fiber.Ctx) error {
|
|||||||
} else {
|
} else {
|
||||||
// password is correct -> generate the JWT
|
// password is correct -> generate the JWT
|
||||||
if jwt, err := config.SignJWT(JWTPayload{
|
if jwt, err := config.SignJWT(JWTPayload{
|
||||||
UserID: requestBody.Username,
|
UserName: requestBody.Username,
|
||||||
TokenID: result.TokenID,
|
TokenID: result.TokenID,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
response.Status = fiber.StatusInternalServerError
|
response.Status = fiber.StatusInternalServerError
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ func init() {
|
|||||||
endpoints := map[string]map[string]func(HandlerArgs) responseMessage{
|
endpoints := map[string]map[string]func(HandlerArgs) responseMessage{
|
||||||
"GET": {
|
"GET": {
|
||||||
"events/assignments": getEventsAssignments,
|
"events/assignments": getEventsAssignments,
|
||||||
|
"events/availabilities": getEventsAvailabilities,
|
||||||
"events/user/pending": getEventsUserPending,
|
"events/user/pending": getEventsUserPending,
|
||||||
"tasks": getTasks,
|
"tasks": getTasks,
|
||||||
},
|
},
|
||||||
@@ -160,7 +161,7 @@ func removeSessionCookie(c *fiber.Ctx) {
|
|||||||
|
|
||||||
// payload of the JSON webtoken
|
// payload of the JSON webtoken
|
||||||
type JWTPayload struct {
|
type JWTPayload struct {
|
||||||
UserID string `json:"userID"`
|
UserName string `json:"userName"`
|
||||||
TokenID string `json:"tokenID"`
|
TokenID string `json:"tokenID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +173,7 @@ type JWT struct {
|
|||||||
|
|
||||||
// extracts the json webtoken from the request
|
// extracts the json webtoken from the request
|
||||||
//
|
//
|
||||||
// @returns (userID, tokenID, error)
|
// @returns (userName, tokenID, error)
|
||||||
func extractJWT(c *fiber.Ctx) (string, string, error) {
|
func extractJWT(c *fiber.Ctx) (string, string, error) {
|
||||||
// get the session-cookie
|
// get the session-cookie
|
||||||
cookie := c.Cookies("session")
|
cookie := c.Cookies("session")
|
||||||
@@ -191,7 +192,7 @@ func extractJWT(c *fiber.Ctx) (string, string, error) {
|
|||||||
|
|
||||||
// extract the claims from the JWT
|
// extract the claims from the JWT
|
||||||
if claims, ok := token.Claims.(*JWT); ok && token.Valid {
|
if claims, ok := token.Claims.(*JWT); ok && token.Valid {
|
||||||
return claims.CustomClaims.UserID, claims.CustomClaims.TokenID, nil
|
return claims.CustomClaims.UserName, claims.CustomClaims.TokenID, nil
|
||||||
} else {
|
} else {
|
||||||
return "", "", fmt.Errorf("invalid JWT")
|
return "", "", fmt.Errorf("invalid JWT")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { DateFormatter as IntlDateFormatter } from "@internationalized/date";
|
import { DateFormatter as IntlDateFormatter } from "@internationalized/date";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
|
import { apiCall } from "./lib";
|
||||||
|
|
||||||
export type Task = string;
|
export type Task = string;
|
||||||
|
|
||||||
@@ -24,7 +25,6 @@ interface Zustand {
|
|||||||
userName: string;
|
userName: string;
|
||||||
admin: boolean;
|
admin: boolean;
|
||||||
} | null;
|
} | null;
|
||||||
tasks?: Record<number, { text: string; disabled: boolean }>;
|
|
||||||
setEvents: (events: EventData[]) => void;
|
setEvents: (events: EventData[]) => void;
|
||||||
reset: (zustand?: Partial<Zustand>) => void;
|
reset: (zustand?: Partial<Zustand>) => void;
|
||||||
setPendingEvents: (c: number) => void;
|
setPendingEvents: (c: number) => void;
|
||||||
@@ -58,6 +58,23 @@ const zustand = create<Zustand>()(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export async function getTasks(): Promise<
|
||||||
|
Record<number, { text: string; disabled: boolean }>
|
||||||
|
> {
|
||||||
|
const result = await apiCall<{ text: string; disabled: boolean }[]>(
|
||||||
|
"GET",
|
||||||
|
"tasks",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.ok) {
|
||||||
|
const tasks = await result.json();
|
||||||
|
|
||||||
|
return tasks;
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class DateFormatter {
|
export class DateFormatter {
|
||||||
private formatter;
|
private formatter;
|
||||||
|
|
||||||
|
|||||||
@@ -6,24 +6,11 @@ import { useState } from "react";
|
|||||||
import AddEvent from "../components/Event/AddEvent";
|
import AddEvent from "../components/Event/AddEvent";
|
||||||
import zustand from "../Zustand";
|
import zustand from "../Zustand";
|
||||||
import AssignmentTable from "@/components/Event/AssignmentTable";
|
import AssignmentTable from "@/components/Event/AssignmentTable";
|
||||||
import { useAsyncList } from "@react-stately/data";
|
|
||||||
import { apiCall } from "@/lib";
|
|
||||||
import { Button } from "@nextui-org/react";
|
import { Button } from "@nextui-org/react";
|
||||||
|
|
||||||
export default function EventVolunteer() {
|
export default function EventVolunteer() {
|
||||||
const [showAddItemDialogue, setShowAddItemDialogue] = useState(false);
|
const [showAddItemDialogue, setShowAddItemDialogue] = useState(false);
|
||||||
|
|
||||||
// fetch the events from the server
|
|
||||||
useAsyncList({
|
|
||||||
load: async () => {
|
|
||||||
const data = await apiCall("GET", "events");
|
|
||||||
|
|
||||||
return {
|
|
||||||
items: [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
<h2 className="mb-4 text-center text-4xl">Overview</h2>
|
<h2 className="mb-4 text-center text-4xl">Overview</h2>
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import AddEvent from "@/components/Event/AddEvent";
|
import AddEvent from "@/components/Event/AddEvent";
|
||||||
import LocalDate from "@/components/LocalDate";
|
import LocalDate from "@/components/LocalDate";
|
||||||
import zustand, { Availability, EventData, Task, Tasks } from "@/Zustand";
|
import { apiCall } from "@/lib";
|
||||||
|
import { Availability, EventData, getTasks, Task } from "@/Zustand";
|
||||||
import { Add, Copy, Edit, TrashCan } from "@carbon/icons-react";
|
import { Add, Copy, Edit, TrashCan } from "@carbon/icons-react";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -25,6 +26,8 @@ import {
|
|||||||
import { useAsyncList } from "@react-stately/data";
|
import { useAsyncList } from "@react-stately/data";
|
||||||
import React, { Key, useState } from "react";
|
import React, { Key, useState } from "react";
|
||||||
|
|
||||||
|
type EventWithAvailabilities = EventData & { availabilities: string[] };
|
||||||
|
|
||||||
function availability2Tailwind(availability?: Availability) {
|
function availability2Tailwind(availability?: Availability) {
|
||||||
switch (availability) {
|
switch (availability) {
|
||||||
case "yes":
|
case "yes":
|
||||||
@@ -46,19 +49,38 @@ function availability2Color(availability?: Availability) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function AdminPanel() {
|
export default function AdminPanel() {
|
||||||
const tasks = [
|
// get the available tasks and craft them into the headers
|
||||||
|
const headers = useAsyncList({
|
||||||
|
async load() {
|
||||||
|
const tasks = await getTasks();
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: [
|
||||||
{ key: "date", label: "Date" },
|
{ key: "date", label: "Date" },
|
||||||
{ key: "description", label: "Description" },
|
{ key: "description", label: "Description" },
|
||||||
...Tasks.map((task) => ({ label: task, key: task })),
|
...Object.entries(tasks)
|
||||||
|
.filter(([, task]) => !task.disabled)
|
||||||
|
.map(([id, task]) => ({ label: task.text, key: id })),
|
||||||
{ key: "actions", label: "Action" },
|
{ key: "actions", label: "Action" },
|
||||||
];
|
],
|
||||||
|
|
||||||
const list = useAsyncList({
|
|
||||||
async load() {
|
|
||||||
return {
|
|
||||||
items: [...zustand.getState().events],
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// get the individual events
|
||||||
|
const events = useAsyncList<EventWithAvailabilities>({
|
||||||
|
async load() {
|
||||||
|
const result = await apiCall<EventWithAvailabilities[]>(
|
||||||
|
"GET",
|
||||||
|
"events/availabilities",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.ok) {
|
||||||
|
return { items: await result.json() };
|
||||||
|
} else {
|
||||||
|
return { items: [] };
|
||||||
|
}
|
||||||
|
},
|
||||||
async sort({ items, sortDescriptor }) {
|
async sort({ items, sortDescriptor }) {
|
||||||
return {
|
return {
|
||||||
items: items.sort((a, b) => {
|
items: items.sort((a, b) => {
|
||||||
@@ -82,7 +104,10 @@ export default function AdminPanel() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function getKeyValue(event: EventData, key: Key): React.ReactNode {
|
function getKeyValue(
|
||||||
|
event: EventWithAvailabilities,
|
||||||
|
key: Key,
|
||||||
|
): React.ReactNode {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "date":
|
case "date":
|
||||||
return (
|
return (
|
||||||
@@ -136,17 +161,17 @@ export default function AdminPanel() {
|
|||||||
}}
|
}}
|
||||||
className="[&_*]:overflow-visible"
|
className="[&_*]:overflow-visible"
|
||||||
>
|
>
|
||||||
{Object.entries(event.volunteers).map(
|
{Object.entries(event.availabilities).map(
|
||||||
([volunteer, availability]) => (
|
([volunteer, availability]) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={volunteer}
|
key={volunteer}
|
||||||
color={availability2Color(availability)}
|
// color={availability2Color(availability)}
|
||||||
className={[
|
className={[
|
||||||
"text-" + availability2Color(availability),
|
// "text-" + availability2Color(availability),
|
||||||
availability2Tailwind(availability),
|
// availability2Tailwind(availability),
|
||||||
].join(" ")}
|
].join(" ")}
|
||||||
>
|
>
|
||||||
{volunteer}
|
{volunteer} ({availability})
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
@@ -157,7 +182,7 @@ export default function AdminPanel() {
|
|||||||
|
|
||||||
const [showAddEvent, setShowAddEvent] = useState(false);
|
const [showAddEvent, setShowAddEvent] = useState(false);
|
||||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||||
const [activeEvent, setActiveEvent] = useState(zustand.getState().events[0]);
|
const [activeEvent, setActiveEvent] = useState<EventData | undefined>();
|
||||||
|
|
||||||
const topContent = (
|
const topContent = (
|
||||||
<div>
|
<div>
|
||||||
@@ -181,8 +206,8 @@ export default function AdminPanel() {
|
|||||||
topContent={topContent}
|
topContent={topContent}
|
||||||
topContentPlacement="outside"
|
topContentPlacement="outside"
|
||||||
isHeaderSticky
|
isHeaderSticky
|
||||||
sortDescriptor={list.sortDescriptor}
|
sortDescriptor={events.sortDescriptor}
|
||||||
onSortChange={list.sort}
|
onSortChange={events.sort}
|
||||||
classNames={{
|
classNames={{
|
||||||
wrapper: "bg-accent-4",
|
wrapper: "bg-accent-4",
|
||||||
tr: "even:bg-accent-5 ",
|
tr: "even:bg-accent-5 ",
|
||||||
@@ -191,7 +216,7 @@ export default function AdminPanel() {
|
|||||||
}}
|
}}
|
||||||
className="w-fit"
|
className="w-fit"
|
||||||
>
|
>
|
||||||
<TableHeader columns={tasks}>
|
<TableHeader columns={headers.items}>
|
||||||
{(task) => (
|
{(task) => (
|
||||||
<TableColumn
|
<TableColumn
|
||||||
allowsSorting={task.key === "date"}
|
allowsSorting={task.key === "date"}
|
||||||
@@ -202,7 +227,7 @@ export default function AdminPanel() {
|
|||||||
</TableColumn>
|
</TableColumn>
|
||||||
)}
|
)}
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody items={list.items} emptyContent={"No events scheduled"}>
|
<TableBody items={events.items} emptyContent={"No events scheduled"}>
|
||||||
{(event) => (
|
{(event) => (
|
||||||
<TableRow key={event.id}>
|
<TableRow key={event.id}>
|
||||||
{(columnKey) => (
|
{(columnKey) => (
|
||||||
@@ -215,6 +240,7 @@ export default function AdminPanel() {
|
|||||||
|
|
||||||
<AddEvent isOpen={showAddEvent} onOpenChange={setShowAddEvent} />
|
<AddEvent isOpen={showAddEvent} onOpenChange={setShowAddEvent} />
|
||||||
|
|
||||||
|
{activeEvent !== undefined ? (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={showDeleteConfirm}
|
isOpen={showDeleteConfirm}
|
||||||
onOpenChange={setShowDeleteConfirm}
|
onOpenChange={setShowDeleteConfirm}
|
||||||
@@ -248,6 +274,7 @@ export default function AdminPanel() {
|
|||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,18 +36,8 @@ export default function RootLayout({
|
|||||||
href: "/assignments",
|
href: "/assignments",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "Assign Tasks",
|
text: "Admin",
|
||||||
href: "/admin/assign",
|
href: "/admin",
|
||||||
admin: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Users",
|
|
||||||
href: "/admin/users",
|
|
||||||
admin: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Configuration",
|
|
||||||
href: "/admin/config",
|
|
||||||
admin: true,
|
admin: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useReducer } from "react";
|
import { useEffect, useReducer } from "react";
|
||||||
import { Add } from "@carbon/icons-react";
|
import { Add } from "@carbon/icons-react";
|
||||||
import zustand, { Task } from "../../Zustand";
|
import zustand, { getTasks, Task } from "../../Zustand";
|
||||||
import { getLocalTimeZone, now, ZonedDateTime } from "@internationalized/date";
|
import { getLocalTimeZone, now, ZonedDateTime } from "@internationalized/date";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -54,20 +54,7 @@ export default function AddEvent(props: {
|
|||||||
|
|
||||||
// get the available tasks
|
// get the available tasks
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
void getTasks();
|
||||||
const result = await apiCall<{ text: string; disabled: boolean }[]>(
|
|
||||||
"GET",
|
|
||||||
"tasks",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.ok) {
|
|
||||||
const tasks = await result.json();
|
|
||||||
|
|
||||||
zustand.setState(() => ({
|
|
||||||
tasks,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// sends the addEvent request to the backend
|
// sends the addEvent request to the backend
|
||||||
|
|||||||
Reference in New Issue
Block a user