started some designing
This commit is contained in:
16
client/src/app/components/Button.tsx
Normal file
16
client/src/app/components/Button.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React, { MouseEventHandler } from "react";
|
||||
|
||||
export default function Button(props: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
onClick?: MouseEventHandler<HTMLDivElement>;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
onClick={props.onClick}
|
||||
className={`${props.className ?? ""} inline-block cursor-pointer rounded-full bg-accent-2 p-2`}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
13
client/src/app/components/CheckBox.tsx
Normal file
13
client/src/app/components/CheckBox.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { CheckboxChecked, Checkbox } from "@carbon/icons-react";
|
||||
import { MouseEventHandler } from "react";
|
||||
|
||||
export default function CheckBox(props: {
|
||||
state: boolean;
|
||||
onClick?: MouseEventHandler<HTMLDivElement>;
|
||||
}) {
|
||||
return (
|
||||
<div onClick={props.onClick} className="inline-block cursor-pointer">
|
||||
{props.state ? <CheckboxChecked /> : <Checkbox />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
64
client/src/app/components/Event/AddEvent.tsx
Normal file
64
client/src/app/components/Event/AddEvent.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { useState } from "react";
|
||||
import CheckBox from "../CheckBox";
|
||||
import { Task } from "./Event";
|
||||
import { AddLarge } from "@carbon/icons-react";
|
||||
import Button from "../Button";
|
||||
|
||||
interface state {
|
||||
date: Date;
|
||||
description: string;
|
||||
tasks: string[];
|
||||
}
|
||||
|
||||
export default function AddEvent(props: { className?: string }) {
|
||||
const availableTasks = Object.keys(Task).filter((tt) => isNaN(Number(tt)));
|
||||
|
||||
const [state, setState] = useState<state>({
|
||||
date: new Date(),
|
||||
description: "",
|
||||
tasks: [],
|
||||
});
|
||||
|
||||
function toggleTask(task: string) {
|
||||
const new_tasks = state.tasks.slice();
|
||||
|
||||
const index = new_tasks.indexOf(task);
|
||||
|
||||
if (index != -1) {
|
||||
new_tasks.splice(index, 1);
|
||||
} else {
|
||||
new_tasks.push(task);
|
||||
}
|
||||
|
||||
setState({
|
||||
...state,
|
||||
tasks: new_tasks,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${props.className ?? ""} flex w-64 flex-col gap-2 rounded-xl bg-accent-5 p-4`}
|
||||
>
|
||||
<h1 className="text-2xl">Add Event</h1>
|
||||
<input type="date" />
|
||||
<input type="text" placeholder="Description" />
|
||||
{availableTasks.map((tt, ii) => (
|
||||
<div key={ii}>
|
||||
<label className="flex items-center gap-2">
|
||||
<input type="checkbox" className="hidden" />
|
||||
<CheckBox
|
||||
state={state.tasks.includes(tt)}
|
||||
onClick={() => toggleTask(tt)}
|
||||
/>
|
||||
{tt}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
<Button className="ml-auto flex w-fit items-center justify-center gap-2 pr-4">
|
||||
<AddLarge size={32} />
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
35
client/src/app/components/Event/Event.tsx
Normal file
35
client/src/app/components/Event/Event.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
export enum Task {
|
||||
Audio,
|
||||
Livestream,
|
||||
Camera,
|
||||
Light,
|
||||
StreamAudio,
|
||||
}
|
||||
|
||||
export interface EventData {
|
||||
id: number;
|
||||
date: Date;
|
||||
tasks: Partial<Record<Task, string | undefined>>;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default function Event(props: EventData) {
|
||||
return (
|
||||
<div key={props.id} className="w-64 rounded-xl bg-accent-5 p-4">
|
||||
<h3 className="bold mb-1 text-2xl">{props.date.toLocaleDateString()}</h3>
|
||||
{props.description !== undefined ? <div>{props.description}</div> : null}
|
||||
<table className="mt-4">
|
||||
<tbody>
|
||||
{Object.entries(props.tasks).map(([task, person], ii) => (
|
||||
<tr key={ii}>
|
||||
<th className="pr-4 text-left">
|
||||
{Task[task as unknown as Task]}
|
||||
</th>
|
||||
<td>{person ?? <span className="text-primary">missing</span>}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
69
client/src/app/components/Overview.tsx
Normal file
69
client/src/app/components/Overview.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
"use client";
|
||||
|
||||
import { AddLarge, CloseLarge } from "@carbon/icons-react";
|
||||
import Event, { EventData } from "./Event/Event";
|
||||
import { useState } from "react";
|
||||
import AddEvent from "./Event/AddEvent";
|
||||
import Button from "./Button";
|
||||
|
||||
export default function EventVolunteer() {
|
||||
const events: EventData[] = [
|
||||
{
|
||||
id: 0,
|
||||
date: new Date("2025-01-05"),
|
||||
tasks: {
|
||||
"0": "Mark",
|
||||
},
|
||||
description: "neuer Prädikant",
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
date: new Date("2025-01-12"),
|
||||
tasks: {
|
||||
"0": "Mark",
|
||||
"1": undefined,
|
||||
},
|
||||
description: "",
|
||||
},
|
||||
];
|
||||
|
||||
const [showAddItemDialogue, setShowAddItemDialogue] = useState(true);
|
||||
|
||||
return (
|
||||
<div className="relative flex-1 p-4">
|
||||
<h2 className="mb-4 text-center text-4xl">Overview</h2>
|
||||
<div className="flex flex-wrap justify-center gap-4">
|
||||
{events.map((ee) => Event(ee))}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className="absolute bottom-0 right-0 aspect-square"
|
||||
onClick={() => setShowAddItemDialogue(true)}
|
||||
>
|
||||
<AddLarge size={32} />
|
||||
</Button>
|
||||
|
||||
{showAddItemDialogue ? (
|
||||
<div
|
||||
className="absolute inset-0 flex flex-col items-center backdrop-blur"
|
||||
onClick={(e) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
setShowAddItemDialogue(false);
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="relative">
|
||||
<AddEvent className="border-2 border-accent-3" />
|
||||
<Button
|
||||
className="absolute right-2 top-2 aspect-square"
|
||||
onClick={() => setShowAddItemDialogue(false)}
|
||||
>
|
||||
<CloseLarge />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
BIN
client/src/app/favicon.ico
Normal file
BIN
client/src/app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
62
client/src/app/globals.css
Normal file
62
client/src/app/globals.css
Normal file
@@ -0,0 +1,62 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@font-face {
|
||||
font-family: "pilowlava";
|
||||
src: URL("/fonts/pilowlava/Fonts/webfonts/Pilowlava-Regular.woff2")
|
||||
format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "spacegrotesk";
|
||||
src: URL("/fonts/space-grotesk-1.1.4/webfont/SpaceGrotesk-Regular.woff2")
|
||||
format("woff2");
|
||||
src: URL("/fonts/space-grotesk-1.1.4/webfont/SpaceGrotesk-Bold.woff2")
|
||||
format("woff2");
|
||||
src: URL("/fonts/space-grotesk-1.1.4/webfont/SpaceGrotesk-Light.woff2")
|
||||
format("woff2");
|
||||
src: URL("/fonts/space-grotesk-1.1.4/webfont/SpaceGrotesk-Medium.woff2")
|
||||
format("woff2");
|
||||
src: URL("/fonts/space-grotesk-1.1.4/webfont/SpaceGrotesk-SemiBold.woff2")
|
||||
format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "space-mono";
|
||||
src: URL("/fonts/space-mono/SpaceMono-Regular.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "uncut-sans";
|
||||
src: URL("/fonts/uncut-sans/Webfonts/UncutSans-Regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary: #ff5053;
|
||||
--highlight: #fef2ff;
|
||||
--accent-1: #b2aaff;
|
||||
--accent-2: #6a5fdb;
|
||||
--accent-3: #261a66;
|
||||
--accent-4: #29114c;
|
||||
--accent-5: #190b2f;
|
||||
--background: #0f000a;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
@apply font-headline text-primary;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply border-2 border-accent-1 bg-transparent;
|
||||
}
|
||||
|
||||
html {
|
||||
@apply bg-background font-body text-highlight;
|
||||
}
|
||||
}
|
||||
19
client/src/app/layout.tsx
Normal file
19
client/src/app/layout.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app"
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className="antialiased">{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
16
client/src/app/page.tsx
Normal file
16
client/src/app/page.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import EventVolunteer from "./components/Overview";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="p-4 min-h-screen flex flex-col">
|
||||
<header>
|
||||
<h1 className="text-center text-8xl font-display-headline">
|
||||
Volunteer schedluer
|
||||
</h1>
|
||||
</header>
|
||||
<main className="min-h-full flex-1 flex">
|
||||
<EventVolunteer />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user