added event-creation
This commit is contained in:
@@ -1,19 +1,13 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-sql-driver/mysql"
|
"github.com/go-sql-driver/mysql"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
_config "github.com/johannesbuehl/golunteer/backend/pkg/config"
|
_config "github.com/johannesbuehl/golunteer/backend/pkg/config"
|
||||||
_logger "github.com/johannesbuehl/golunteer/backend/pkg/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var logger = _logger.Logger
|
|
||||||
var config = _config.Config
|
var config = _config.Config
|
||||||
|
|
||||||
// connection to database
|
// connection to database
|
||||||
@@ -37,189 +31,3 @@ func init() {
|
|||||||
DB.SetConnMaxLifetime(time.Minute)
|
DB.SetConnMaxLifetime(time.Minute)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// query the database
|
|
||||||
func SelectOld[T any](table string, where string, args ...any) ([]T, error) {
|
|
||||||
// validate columns against struct T
|
|
||||||
tType := reflect.TypeOf(new(T)).Elem()
|
|
||||||
columns := make([]string, tType.NumField())
|
|
||||||
|
|
||||||
validColumns := make(map[string]any)
|
|
||||||
for ii := 0; ii < tType.NumField(); ii++ {
|
|
||||||
field := tType.Field(ii)
|
|
||||||
validColumns[strings.ToLower(field.Name)] = struct{}{}
|
|
||||||
columns[ii] = strings.ToLower(field.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, col := range columns {
|
|
||||||
if _, ok := validColumns[strings.ToLower(col)]; !ok {
|
|
||||||
return nil, fmt.Errorf("invalid column: %s for struct type %T", col, new(T))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the query
|
|
||||||
completeQuery := fmt.Sprintf("SELECT %s FROM %s", strings.Join(columns, ", "), table)
|
|
||||||
|
|
||||||
if where != "" && where != "*" {
|
|
||||||
completeQuery = fmt.Sprintf("%s WHERE %s", completeQuery, where)
|
|
||||||
}
|
|
||||||
|
|
||||||
var rows *sql.Rows
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if len(args) > 0 {
|
|
||||||
DB.Ping()
|
|
||||||
|
|
||||||
rows, err = DB.Query(completeQuery, args...)
|
|
||||||
} else {
|
|
||||||
DB.Ping()
|
|
||||||
|
|
||||||
rows, err = DB.Query(completeQuery)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logger.Error().Msgf("database access failed with error %v", err)
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer rows.Close()
|
|
||||||
results := []T{}
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var lineResult T
|
|
||||||
|
|
||||||
scanArgs := make([]any, len(columns))
|
|
||||||
v := reflect.ValueOf(&lineResult).Elem()
|
|
||||||
|
|
||||||
for ii, col := range columns {
|
|
||||||
field := v.FieldByName(col)
|
|
||||||
|
|
||||||
if field.IsValid() && field.CanSet() {
|
|
||||||
scanArgs[ii] = field.Addr().Interface()
|
|
||||||
} else {
|
|
||||||
logger.Warn().Msgf("Field %s not found in struct %T", col, lineResult)
|
|
||||||
scanArgs[ii] = new(any) // save dummy value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// scan the row into the struct
|
|
||||||
if err := rows.Scan(scanArgs...); err != nil {
|
|
||||||
logger.Warn().Msgf("Scan-error: %v", err)
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
results = append(results, lineResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
logger.Error().Msgf("rows-error: %v", err)
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert data intot the databse
|
|
||||||
func Insert(table string, vals any) error {
|
|
||||||
// extract columns from vals
|
|
||||||
v := reflect.ValueOf(vals)
|
|
||||||
t := v.Type()
|
|
||||||
|
|
||||||
columns := make([]string, t.NumField())
|
|
||||||
values := make([]any, t.NumField())
|
|
||||||
|
|
||||||
for ii := 0; ii < t.NumField(); ii++ {
|
|
||||||
fieldValue := v.Field(ii)
|
|
||||||
|
|
||||||
field := t.Field(ii)
|
|
||||||
|
|
||||||
columns[ii] = strings.ToLower(field.Name)
|
|
||||||
values[ii] = fieldValue.Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
placeholders := strings.Repeat(("?, "), len(columns))
|
|
||||||
placeholders = strings.TrimSuffix(placeholders, ", ")
|
|
||||||
|
|
||||||
completeQuery := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", table, strings.Join(columns, ", "), placeholders)
|
|
||||||
|
|
||||||
_, err := DB.Exec(completeQuery, values...)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// update data in the database
|
|
||||||
func Update(table string, set, where any) error {
|
|
||||||
setV := reflect.ValueOf(set)
|
|
||||||
setT := setV.Type()
|
|
||||||
|
|
||||||
setColumns := make([]string, setT.NumField())
|
|
||||||
setValues := make([]any, setT.NumField())
|
|
||||||
|
|
||||||
for ii := 0; ii < setT.NumField(); ii++ {
|
|
||||||
fieldValue := setV.Field(ii)
|
|
||||||
|
|
||||||
field := setT.Field(ii)
|
|
||||||
|
|
||||||
setColumns[ii] = strings.ToLower(field.Name) + " = ?"
|
|
||||||
setValues[ii] = fieldValue.Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
whereV := reflect.ValueOf(where)
|
|
||||||
whereT := whereV.Type()
|
|
||||||
|
|
||||||
whereColumns := make([]string, whereT.NumField())
|
|
||||||
whereValues := make([]any, whereT.NumField())
|
|
||||||
|
|
||||||
for ii := 0; ii < whereT.NumField(); ii++ {
|
|
||||||
fieldValue := whereV.Field(ii)
|
|
||||||
|
|
||||||
// skip empty (zero) values
|
|
||||||
if !fieldValue.IsZero() {
|
|
||||||
field := whereT.Field(ii)
|
|
||||||
|
|
||||||
whereColumns[ii] = strings.ToLower(field.Name) + " = ?"
|
|
||||||
whereValues[ii] = fmt.Sprint(fieldValue.Interface())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sets := strings.Join(setColumns, ", ")
|
|
||||||
wheres := strings.Join(whereColumns, " AND ")
|
|
||||||
|
|
||||||
placeholderValues := append(setValues, whereValues...)
|
|
||||||
|
|
||||||
completeQuery := fmt.Sprintf("UPDATE %s SET %s WHERE %s", table, sets, wheres)
|
|
||||||
|
|
||||||
_, err := DB.Exec(completeQuery, placeholderValues...)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove data from the database
|
|
||||||
func Delete(table string, vals any) error {
|
|
||||||
// extract columns from vals
|
|
||||||
v := reflect.ValueOf(vals)
|
|
||||||
t := v.Type()
|
|
||||||
|
|
||||||
columns := make([]string, t.NumField())
|
|
||||||
values := make([]any, t.NumField())
|
|
||||||
|
|
||||||
for ii := 0; ii < t.NumField(); ii++ {
|
|
||||||
fieldValue := v.Field(ii)
|
|
||||||
|
|
||||||
// skip empty (zero) values
|
|
||||||
if !fieldValue.IsZero() {
|
|
||||||
field := t.Field(ii)
|
|
||||||
|
|
||||||
columns[ii] = strings.ToLower(field.Name) + " = ?"
|
|
||||||
values[ii] = fmt.Sprint(fieldValue.Interface())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
completeQuery := fmt.Sprintf("DELETE FROM %s WHERE %s", table, strings.Join(columns, ", "))
|
|
||||||
|
|
||||||
_, err := DB.Exec(completeQuery, values...)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type EventWithAssignment struct {
|
|||||||
|
|
||||||
type eventDataDB struct {
|
type eventDataDB struct {
|
||||||
Id int `db:"id" json:"id"`
|
Id int `db:"id" json:"id"`
|
||||||
Date string `db:"date" json:"date"`
|
Date string `db:"date" json:"date" validate:"required"`
|
||||||
Description string `db:"description" json:"description"`
|
Description string `db:"description" json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,6 +30,45 @@ func (e *eventDataDB) Event() (EventWithAssignment, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EventCreate struct {
|
||||||
|
eventDataDB
|
||||||
|
Tasks []int `json:"tasks" validate:"required,min=1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Create(event EventCreate) error {
|
||||||
|
if result, err := db.DB.NamedExec("INSERT INTO EVENTS (date, description) VALUES (:date, :description)", event); err != nil {
|
||||||
|
return err
|
||||||
|
} else if id, err := result.LastInsertId(); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
// create an insert-slice with the id included
|
||||||
|
tasks := []struct {
|
||||||
|
TaskID int `db:"taskID"`
|
||||||
|
EventID int64 `db:"eventID"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
for _, taskID := range event.Tasks {
|
||||||
|
tasks = append(tasks, struct {
|
||||||
|
TaskID int "db:\"taskID\""
|
||||||
|
EventID int64 "db:\"eventID\""
|
||||||
|
}{
|
||||||
|
TaskID: taskID,
|
||||||
|
EventID: id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the assignments
|
||||||
|
if _, err := db.DB.NamedExec("INSERT INTO USER_ASSIGNMENTS (eventID, taskID) VALUES (:eventID, :taskID)", tasks); err != nil {
|
||||||
|
// delete the event again
|
||||||
|
db.DB.Query("DELETE FROM EVENTS WHERE id = ?", id)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func All() ([]eventDataDB, error) {
|
func All() ([]eventDataDB, error) {
|
||||||
var dbRows []eventDataDB
|
var dbRows []eventDataDB
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ type tasksDB struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Task struct {
|
type Task struct {
|
||||||
Text string
|
Text string `json:"text"`
|
||||||
Disabled bool
|
Disabled bool `json:"disabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var c *cache.Cache
|
var c *cache.Cache
|
||||||
|
|||||||
@@ -5,6 +5,43 @@ import (
|
|||||||
"github.com/johannesbuehl/golunteer/backend/pkg/db/events"
|
"github.com/johannesbuehl/golunteer/backend/pkg/db/events"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func postEvent(args HandlerArgs) responseMessage {
|
||||||
|
response := responseMessage{}
|
||||||
|
|
||||||
|
// write the event
|
||||||
|
var body events.EventCreate
|
||||||
|
|
||||||
|
// try to parse the body
|
||||||
|
if err := args.C.BodyParser(&body); err != nil {
|
||||||
|
response.Status = fiber.StatusBadRequest
|
||||||
|
|
||||||
|
logger.Log().Msgf("can't parse body: %v", err)
|
||||||
|
|
||||||
|
// validate the parsed body
|
||||||
|
} else if err := validate.Struct(body); err != nil {
|
||||||
|
response.Status = fiber.StatusBadRequest
|
||||||
|
|
||||||
|
logger.Log().Msgf("invalid body: %v", err)
|
||||||
|
|
||||||
|
// create the event
|
||||||
|
} else if err := events.Create(body); err != nil {
|
||||||
|
response.Status = fiber.StatusInternalServerError
|
||||||
|
|
||||||
|
logger.Error().Msgf("can't create event: %v", err)
|
||||||
|
} else {
|
||||||
|
// respond with the new events
|
||||||
|
if events, err := events.WithAssignments(); err != nil {
|
||||||
|
response.Status = fiber.StatusInternalServerError
|
||||||
|
|
||||||
|
logger.Error().Msgf("can't retrieve events: %v", err)
|
||||||
|
} else {
|
||||||
|
response.Data = events
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
func getEventsAssignments(args HandlerArgs) responseMessage {
|
func getEventsAssignments(args HandlerArgs) responseMessage {
|
||||||
response := responseMessage{}
|
response := responseMessage{}
|
||||||
|
|
||||||
|
|||||||
@@ -74,8 +74,12 @@ func init() {
|
|||||||
|
|
||||||
// map with the individual registered endpoints
|
// map with the individual registered endpoints
|
||||||
endpoints := map[string]map[string]func(HandlerArgs) responseMessage{
|
endpoints := map[string]map[string]func(HandlerArgs) responseMessage{
|
||||||
"GET": {"events/assignments": getEventsAssignments, "events/user/pending": getEventsUserPending},
|
"GET": {
|
||||||
"POST": {},
|
"events/assignments": getEventsAssignments,
|
||||||
|
"events/user/pending": getEventsUserPending,
|
||||||
|
"tasks": getTasks,
|
||||||
|
},
|
||||||
|
"POST": {"events": postEvent},
|
||||||
"PATCH": {},
|
"PATCH": {},
|
||||||
"DELETE": {},
|
"DELETE": {},
|
||||||
}
|
}
|
||||||
|
|||||||
20
backend/pkg/router/tasks.go
Normal file
20
backend/pkg/router/tasks.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/johannesbuehl/golunteer/backend/pkg/db/tasks"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getTasks(args HandlerArgs) responseMessage {
|
||||||
|
response := responseMessage{}
|
||||||
|
|
||||||
|
if tasks, err := tasks.Get(); err != nil {
|
||||||
|
response.Status = fiber.StatusInternalServerError
|
||||||
|
|
||||||
|
logger.Error().Msgf("can't get tasks: %v", err)
|
||||||
|
} else {
|
||||||
|
response.Data = tasks
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
@@ -6,14 +6,6 @@ import { persist } from "zustand/middleware";
|
|||||||
|
|
||||||
export type Task = string;
|
export type Task = string;
|
||||||
|
|
||||||
export const Tasks: Task[] = [
|
|
||||||
"Audio",
|
|
||||||
"Livestream",
|
|
||||||
"Camera",
|
|
||||||
"Light",
|
|
||||||
"Stream Audio",
|
|
||||||
];
|
|
||||||
|
|
||||||
export type Availability = string;
|
export type Availability = string;
|
||||||
|
|
||||||
export const Availabilities: Availability[] = ["yes", "maybe", "no"];
|
export const Availabilities: Availability[] = ["yes", "maybe", "no"];
|
||||||
@@ -21,7 +13,7 @@ export const Availabilities: Availability[] = ["yes", "maybe", "no"];
|
|||||||
export interface EventData {
|
export interface EventData {
|
||||||
id: number;
|
id: number;
|
||||||
date: string;
|
date: string;
|
||||||
tasks: Partial<Record<Task, string | undefined>>;
|
tasks: Partial<Record<Task, string | null>>;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +24,7 @@ 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;
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ export default function Events() {
|
|||||||
<div className="flex flex-wrap justify-center gap-4">
|
<div className="flex flex-wrap justify-center gap-4">
|
||||||
{events.map((ee, ii) => (
|
{events.map((ee, ii) => (
|
||||||
<Event key={ii} event={ee}>
|
<Event key={ii} event={ee}>
|
||||||
|
<div className="mt-auto">
|
||||||
<AssignmentTable tasks={ee.tasks} />
|
<AssignmentTable tasks={ee.tasks} />
|
||||||
|
</div>
|
||||||
</Event>
|
</Event>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
import { useState } from "react";
|
import { useEffect, useReducer } from "react";
|
||||||
import { Add } from "@carbon/icons-react";
|
import { Add } from "@carbon/icons-react";
|
||||||
import zustand, { EventData, Task, Tasks } from "../../Zustand";
|
import zustand, { Task } from "../../Zustand";
|
||||||
import { getLocalTimeZone, now, ZonedDateTime } from "@internationalized/date";
|
import { getLocalTimeZone, now, ZonedDateTime } from "@internationalized/date";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
CheckboxGroup,
|
CheckboxGroup,
|
||||||
DatePicker,
|
DatePicker,
|
||||||
|
Form,
|
||||||
Modal,
|
Modal,
|
||||||
ModalBody,
|
ModalBody,
|
||||||
ModalContent,
|
ModalContent,
|
||||||
ModalFooter,
|
ModalFooter,
|
||||||
ModalHeader,
|
ModalHeader,
|
||||||
|
Spinner,
|
||||||
Textarea,
|
Textarea,
|
||||||
} from "@nextui-org/react";
|
} from "@nextui-org/react";
|
||||||
|
import { apiCall } from "@/lib";
|
||||||
|
|
||||||
interface state {
|
interface state {
|
||||||
date: ZonedDateTime;
|
date: ZonedDateTime;
|
||||||
@@ -21,33 +24,75 @@ interface state {
|
|||||||
tasks: Task[];
|
tasks: Task[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface dispatchAction {
|
||||||
|
action: "set" | "reset";
|
||||||
|
value?: Partial<state>;
|
||||||
|
}
|
||||||
|
|
||||||
export default function AddEvent(props: {
|
export default function AddEvent(props: {
|
||||||
className?: string;
|
className?: string;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onOpenChange: (isOpen: boolean) => void;
|
onOpenChange: (isOpen: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const [state, setState] = useState<state>({
|
// initial state for the inputs
|
||||||
|
const initialState: state = {
|
||||||
date: now(getLocalTimeZone()),
|
date: now(getLocalTimeZone()),
|
||||||
description: "",
|
description: "",
|
||||||
tasks: [],
|
tasks: [],
|
||||||
});
|
|
||||||
|
|
||||||
function addEvent() {
|
|
||||||
const eventData: EventData = {
|
|
||||||
date: state.date.toString(),
|
|
||||||
description: state.description,
|
|
||||||
id: zustand.getState().events.slice(-1)[0].id + 1,
|
|
||||||
tasks: {},
|
|
||||||
volunteers: {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// add all the tasks
|
// handle state dispatches
|
||||||
state.tasks.forEach((task) => {
|
function reducer(state: state, action: dispatchAction): state {
|
||||||
eventData.tasks[task] = undefined;
|
if (action.action === "reset") {
|
||||||
});
|
return initialState;
|
||||||
|
} else {
|
||||||
zustand.getState().addEvent(eventData);
|
return { ...state, ...action.value };
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
const [state, dispatchState] = useReducer(reducer, initialState);
|
||||||
|
const tasks = zustand((state) => state.tasks);
|
||||||
|
|
||||||
|
// get the available tasks
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
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
|
||||||
|
async function addEvent() {
|
||||||
|
const data = {
|
||||||
|
...state,
|
||||||
|
tasks: state.tasks.map((task) => parseInt(task)),
|
||||||
|
date: state.date.toAbsoluteString().slice(0, -1),
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await apiCall("POST", "events", undefined, data);
|
||||||
|
|
||||||
|
if (result.ok) {
|
||||||
|
zustand.getState().setEvents(await result.json());
|
||||||
|
|
||||||
|
props.onOpenChange(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset the state when the modal gets closed
|
||||||
|
useEffect(() => {
|
||||||
|
if (!props.isOpen) {
|
||||||
|
dispatchState({ action: "reset" });
|
||||||
|
}
|
||||||
|
}, [props.isOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -58,37 +103,64 @@ export default function AddEvent(props: {
|
|||||||
classNames={{
|
classNames={{
|
||||||
base: "bg-accent-5 ",
|
base: "bg-accent-5 ",
|
||||||
}}
|
}}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
validationBehavior="native"
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
void addEvent();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
<h1 className="text-2xl">Add Event</h1>
|
<h1 className="text-center text-2xl">Add Event</h1>
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
|
isRequired
|
||||||
label="Event date"
|
label="Event date"
|
||||||
|
name="date"
|
||||||
variant="bordered"
|
variant="bordered"
|
||||||
hideTimeZone
|
hideTimeZone
|
||||||
granularity="minute"
|
granularity="minute"
|
||||||
value={state.date}
|
value={state.date}
|
||||||
onChange={(dt) => (!!dt ? setState({ ...state, date: dt }) : null)}
|
onChange={(dt) =>
|
||||||
|
!!dt
|
||||||
|
? dispatchState({ action: "set", value: { date: dt } })
|
||||||
|
: null
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Textarea
|
<Textarea
|
||||||
variant="bordered"
|
variant="bordered"
|
||||||
placeholder="Description"
|
placeholder="Description"
|
||||||
|
name="description"
|
||||||
value={state.description}
|
value={state.description}
|
||||||
onValueChange={(desc) => setState({ ...state, description: desc })}
|
onValueChange={(s) =>
|
||||||
|
dispatchState({ action: "set", value: { description: s } })
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<CheckboxGroup
|
<CheckboxGroup
|
||||||
value={state.tasks}
|
value={state.tasks}
|
||||||
onValueChange={(newTasks) =>
|
name="tasks"
|
||||||
setState({ ...state, tasks: newTasks })
|
onValueChange={(s) =>
|
||||||
|
dispatchState({ action: "set", value: { tasks: s } })
|
||||||
|
}
|
||||||
|
validate={(value) =>
|
||||||
|
value.length > 0 ? true : "Atleast one task must be selected"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{Tasks.map((task, ii) => (
|
{tasks !== undefined ? (
|
||||||
<div key={ii}>
|
Object.entries(tasks)
|
||||||
<Checkbox value={task}>{task}</Checkbox>
|
.filter(([, task]) => !task.disabled)
|
||||||
|
.map(([id, task]) => (
|
||||||
|
<div key={id}>
|
||||||
|
<Checkbox value={id}>{task.text}</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))
|
||||||
|
) : (
|
||||||
|
<Spinner label="Loading" />
|
||||||
|
)}
|
||||||
</CheckboxGroup>
|
</CheckboxGroup>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
@@ -96,12 +168,13 @@ export default function AddEvent(props: {
|
|||||||
color="primary"
|
color="primary"
|
||||||
radius="full"
|
radius="full"
|
||||||
startContent={<Add size={32} />}
|
startContent={<Add size={32} />}
|
||||||
onPress={addEvent}
|
type="submit"
|
||||||
>
|
>
|
||||||
Add
|
Add
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user