added final frontend with full responsive style but without any logic
This commit is contained in:
20
Mock/frontendMockData.json
Normal file
20
Mock/frontendMockData.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Mock Book 1",
|
||||||
|
"author": "Author 1",
|
||||||
|
"description": "Description for Mock Book 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "Mock Book 2",
|
||||||
|
"author": "Author 2",
|
||||||
|
"description": "Description for Mock Book 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"title": "Mock Book 3",
|
||||||
|
"author": "Author 3",
|
||||||
|
"description": "Description for Mock Book 3"
|
||||||
|
}
|
||||||
|
]
|
@@ -1,10 +1,49 @@
|
|||||||
import "./App.css";
|
import "./App.css";
|
||||||
|
import Layout from "./layout/Layout";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import Form1 from "./components/Form1";
|
||||||
|
import Form2 from "./components/Form2";
|
||||||
|
import Form3 from "./components/Form3";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const Items = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: "Mock Book 1",
|
||||||
|
author: "Author 1",
|
||||||
|
description: "Description for Mock Book 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: "Mock Book 2",
|
||||||
|
author: "Author 2",
|
||||||
|
description: "Description for Mock Book 2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: "Mock Book 3",
|
||||||
|
author: "Author 3",
|
||||||
|
description: "Description for Mock Book 3",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem("allItems", JSON.stringify(Items));
|
||||||
|
localStorage.setItem("borrowableItems", JSON.stringify(Items));
|
||||||
|
localStorage.setItem("borrowCode", "123456");
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Layout>
|
||||||
<h1 className="text-3xl font-bold underline">Hello world!</h1>
|
{/* Mock flow without real logic: show the three sections stacked for design preview */}
|
||||||
</>
|
<div className="space-y-10">
|
||||||
|
<Form1 />
|
||||||
|
<div className="h-px bg-blue-100" />
|
||||||
|
<Form2 />
|
||||||
|
<div className="h-px bg-blue-100" />
|
||||||
|
<Form3 />
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
45
frontend/src/components/Form1.tsx
Normal file
45
frontend/src/components/Form1.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const Form1: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<h2 className="text-xl font-bold text-blue-700">1. Zeitraum wählen</h2>
|
||||||
|
<form className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="startDate"
|
||||||
|
className="block text-sm font-medium text-blue-800 mb-1"
|
||||||
|
>
|
||||||
|
Start
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
id="startDate"
|
||||||
|
className="w-full border border-blue-200 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-400 focus:outline-none bg-white/70"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="endDate"
|
||||||
|
className="block text-sm font-medium text-blue-800 mb-1"
|
||||||
|
>
|
||||||
|
Ende
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
id="endDate"
|
||||||
|
className="w-full border border-blue-200 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-400 focus:outline-none bg-white/70"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full bg-gradient-to-r from-blue-600 to-blue-400 hover:from-blue-700 hover:to-blue-500 text-white font-bold py-2 px-4 rounded-xl shadow transition"
|
||||||
|
>
|
||||||
|
Verfügbare Gegenstände anzeigen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Form1;
|
61
frontend/src/components/Form2.tsx
Normal file
61
frontend/src/components/Form2.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const Form2: React.FC = () => {
|
||||||
|
const items = JSON.parse(localStorage.getItem("borrowableItems") || "[]");
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<h2 className="text-xl font-bold text-blue-700">
|
||||||
|
2. Gegenstand auswählen
|
||||||
|
</h2>
|
||||||
|
<form className="space-y-4">
|
||||||
|
{items.length === 0 ? (
|
||||||
|
<div className="text-red-600 font-medium text-center bg-red-50 border border-red-200 rounded-xl p-4">
|
||||||
|
Keine Gegenstände verfügbar für diesen Zeitraum.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
{items.map((item: any) => (
|
||||||
|
<label
|
||||||
|
key={item.id}
|
||||||
|
htmlFor={String(item.id)}
|
||||||
|
className="group cursor-pointer bg-white/80 rounded-xl p-4 shadow hover:shadow-md transition border border-blue-100 flex items-start gap-3"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name={String(item.id)}
|
||||||
|
id={String(item.id)}
|
||||||
|
className="mt-1 h-4 w-4 rounded border-blue-300 text-blue-600 focus:ring-blue-400"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-sm font-semibold text-blue-800">
|
||||||
|
{item.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-blue-500/80 line-clamp-2">
|
||||||
|
{item.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex flex-col sm:flex-row gap-3 pt-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex-1 sm:flex-none sm:w-36 bg-white text-blue-700 border border-blue-200 hover:bg-blue-50 font-medium py-2 px-4 rounded-xl shadow-sm transition"
|
||||||
|
>
|
||||||
|
Zurück
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="flex-1 sm:flex-none sm:w-40 bg-gradient-to-r from-blue-600 to-blue-400 hover:from-blue-700 hover:to-blue-500 text-white font-bold py-2 px-4 rounded-xl shadow transition"
|
||||||
|
>
|
||||||
|
Ausleihen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Form2;
|
29
frontend/src/components/Form3.tsx
Normal file
29
frontend/src/components/Form3.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { myToast } from "../utils/toastify";
|
||||||
|
|
||||||
|
const Form3: React.FC = () => {
|
||||||
|
if (localStorage.getItem("borrowCode")) {
|
||||||
|
myToast("Ausleihe erfolgreich!", "success");
|
||||||
|
} else {
|
||||||
|
myToast("Ausleihe fehlgeschlagen!", "error");
|
||||||
|
}
|
||||||
|
|
||||||
|
const code = localStorage.getItem("borrowCode") ?? "—";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h2 className="text-xl font-bold text-blue-700">3. Ausleihe bestätigt</h2>
|
||||||
|
<div className="mt-2 p-6 bg-blue-50/80 border border-blue-200 rounded-2xl text-center shadow-lg">
|
||||||
|
<p className="text-blue-800 font-semibold">Ihr Ausleihcode lautet</p>
|
||||||
|
<div className="text-3xl font-extrabold tracking-widest text-blue-700 mt-1">
|
||||||
|
{code}
|
||||||
|
</div>
|
||||||
|
<p className="text-blue-600 mt-2 text-sm">
|
||||||
|
Bitte merken Sie sich diesen Code, um das Schließfach zu öffnen.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Form3;
|
@@ -2,8 +2,13 @@ import React from "react";
|
|||||||
|
|
||||||
const Header: React.FC = () => {
|
const Header: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<header>
|
<header className="mb-6 md:mb-10">
|
||||||
<h1>Header</h1>
|
<h1 className="text-3xl md:text-4xl font-extrabold text-blue-800 tracking-tight drop-shadow-sm">
|
||||||
|
Gegenstand ausleihen
|
||||||
|
</h1>
|
||||||
|
<p className="text-blue-500 mt-1 md:mt-2 text-base md:text-lg font-medium">
|
||||||
|
Schnell und unkompliziert Equipment reservieren
|
||||||
|
</p>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
20
frontend/src/components/Object.tsx
Normal file
20
frontend/src/components/Object.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
type ObjectProps = {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Object: React.FC<ObjectProps> = ({ title, description }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="shrink-0 w-3 h-3 mt-1.5 rounded-full bg-green-400" />
|
||||||
|
<div>
|
||||||
|
<h3 className="text-sm font-semibold text-blue-800">{title}</h3>
|
||||||
|
<p className="text-xs text-blue-500/80 line-clamp-2">{description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Object;
|
47
frontend/src/components/Sidebar.tsx
Normal file
47
frontend/src/components/Sidebar.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Object from "./Object";
|
||||||
|
|
||||||
|
const Sidebar: React.FC = () => {
|
||||||
|
const items = JSON.parse(localStorage.getItem("allItems") || "[]");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside className="w-full md:w-80 md:min-h-screen bg-white/90 backdrop-blur md:border-r border-blue-100 shadow-xl flex flex-col p-6">
|
||||||
|
<h2 className="text-2xl font-extrabold mb-4 text-blue-700 tracking-tight flex items-center gap-2">
|
||||||
|
<svg
|
||||||
|
className="w-6 h-6 text-blue-500"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth={2}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M16.5 7.5V4.75A2.25 2.25 0 0 0 14.25 2.5h-4.5A2.25 2.25 0 0 0 7.5 4.75V7.5m9 0h-9m9 0v11.75A2.25 2.25 0 0 1 14.25 21.5h-4.5A2.25 2.25 0 0 1 7.5 19.25V7.5m9 0h-9"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Ausleih-Übersicht
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="space-y-4 flex-1 overflow-auto pr-1">
|
||||||
|
{items.map((item: any) => (
|
||||||
|
<div
|
||||||
|
key={item.id}
|
||||||
|
className="bg-white/80 rounded-xl p-4 shadow hover:shadow-md transition"
|
||||||
|
>
|
||||||
|
<Object title={item.title} description={item.description} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 text-xs text-gray-400 flex items-center gap-4">
|
||||||
|
<span className="inline-block w-3 h-3 bg-green-400 rounded-full"></span>
|
||||||
|
Verfügbar
|
||||||
|
<span className="inline-block w-3 h-3 bg-blue-400 rounded-full"></span>
|
||||||
|
Verliehen
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Sidebar;
|
@@ -1,7 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import Header from "../components/Header";
|
|
||||||
import "../App.css";
|
import "../App.css";
|
||||||
import { ToastContainer } from "react-toastify";
|
import { ToastContainer } from "react-toastify";
|
||||||
|
import Header from "../components/Header";
|
||||||
|
import Sidebar from "../components/Sidebar";
|
||||||
|
|
||||||
type LayoutProps = {
|
type LayoutProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -9,13 +10,32 @@ type LayoutProps = {
|
|||||||
|
|
||||||
const Layout: React.FC<LayoutProps> = ({ children }) => {
|
const Layout: React.FC<LayoutProps> = ({ children }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="min-h-screen flex bg-gradient-to-r from-blue-50 via-white to-blue-100">
|
||||||
<Header />
|
{/* Sidebar */}
|
||||||
<main>{children}</main>
|
<div className="hidden md:block">
|
||||||
<footer></footer>
|
<Sidebar />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main */}
|
||||||
|
<main className="flex-1 flex flex-col items-center py-10 px-4 md:py-14">
|
||||||
|
<div className="w-full max-w-3xl">
|
||||||
|
<Header />
|
||||||
|
</div>
|
||||||
|
<div className="w-full max-w-3xl bg-white/90 shadow-2xl rounded-3xl p-6 md:p-10 ring-1 ring-blue-100">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* Mobile sidebar at bottom */}
|
||||||
|
<div className="fixed bottom-4 left-4 right-4 md:hidden z-10">
|
||||||
|
<div className="bg-white/95 backdrop-blur rounded-2xl shadow-xl ring-1 ring-blue-100 p-4">
|
||||||
|
<Sidebar />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ToastContainer
|
<ToastContainer
|
||||||
position="top-right"
|
position="top-right"
|
||||||
autoClose={5000}
|
autoClose={3000}
|
||||||
hideProgressBar={false}
|
hideProgressBar={false}
|
||||||
newestOnTop
|
newestOnTop
|
||||||
closeOnClick
|
closeOnClick
|
||||||
@@ -24,8 +44,9 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
|
|||||||
draggable
|
draggable
|
||||||
pauseOnHover
|
pauseOnHover
|
||||||
theme="light"
|
theme="light"
|
||||||
|
className="!z-50"
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
0
frontend/src/utils/fetchData.ts
Normal file
0
frontend/src/utils/fetchData.ts
Normal file
17
frontend/src/utils/toastify.ts
Normal file
17
frontend/src/utils/toastify.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { toast, type ToastOptions } from "react-toastify";
|
||||||
|
|
||||||
|
export type ToastType = "success" | "error" | "info" | "warning";
|
||||||
|
|
||||||
|
export const myToast = (message: string, msgType: ToastType) => {
|
||||||
|
let config: ToastOptions = {
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 5000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
};
|
||||||
|
toast[msgType](message, config);
|
||||||
|
};
|
Reference in New Issue
Block a user