feat: add auth, mock data and zustand stores
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
'use client';
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
import { User } from '../types';
|
||||
import { MOCK_USERS } from './mock-data';
|
||||
|
||||
interface AuthState {
|
||||
user: User | null;
|
||||
login: (username: string, password: string) => boolean;
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
user: null,
|
||||
login: (username, password) => {
|
||||
if (!username || !password) return false;
|
||||
if (password !== username) return false;
|
||||
const foundUser = MOCK_USERS.find(
|
||||
(u) => u.username.toLowerCase() === username.toLowerCase()
|
||||
);
|
||||
if (!foundUser) return false;
|
||||
set({ user: foundUser });
|
||||
return true;
|
||||
},
|
||||
logout: () => set({ user: null }),
|
||||
}),
|
||||
{
|
||||
name: 'patchpilot:auth',
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
partialize: (state) => ({ user: state.user }),
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,225 @@
|
||||
import type { Manual, Tool, User } from '../types';
|
||||
|
||||
export const MOCK_USERS: User[] = [
|
||||
{
|
||||
id: '1',
|
||||
username: 'admin',
|
||||
role: 'admin',
|
||||
trainings: ['drill', '3d-print', 'laser', 'cnc', 'soldering'],
|
||||
email: 'admin@repaircafe.dev',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
username: 'guest',
|
||||
role: 'guest',
|
||||
trainings: [],
|
||||
email: 'guest@repaircafe.dev',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
username: 'staff',
|
||||
role: 'staff',
|
||||
trainings: ['drill', 'soldering'],
|
||||
email: 'staff@repaircafe.dev',
|
||||
},
|
||||
];
|
||||
|
||||
export const MOCK_TOOLS: Tool[] = [
|
||||
{
|
||||
id: 't1',
|
||||
name: 'Bosch Schlagbohrmaschine',
|
||||
description:
|
||||
'Leistungsstarke Schlagbohrmaschine 850 W für Beton, Stein und Mauerwerk. Inklusive Bohrer-Set bis 13 mm.',
|
||||
location: 'Werkstatt A · Regal 2',
|
||||
category: 'power',
|
||||
trainingRequired: true,
|
||||
requiredTrainingId: 'drill',
|
||||
homeAllowed: true,
|
||||
maxRentalDays: 3,
|
||||
emoji: '🪛',
|
||||
},
|
||||
{
|
||||
id: 't2',
|
||||
name: '3D-Drucker Prusa i3 MK3S',
|
||||
description:
|
||||
'Präziser FDM-Drucker für PLA, PETG und ABS. Druckraum 250×210×210 mm. PLA-Filament ist verfügbar.',
|
||||
location: 'Labor 1 · Schrank B',
|
||||
category: 'electronics',
|
||||
trainingRequired: true,
|
||||
requiredTrainingId: '3d-print',
|
||||
homeAllowed: false,
|
||||
maxRentalDays: 1,
|
||||
emoji: '🖨️',
|
||||
},
|
||||
{
|
||||
id: 't3',
|
||||
name: 'Schlosserhammer 500 g',
|
||||
description:
|
||||
'Klassischer Schlosserhammer mit Hickory-Stiel. Für allgemeine Heimwerker-Arbeiten geeignet.',
|
||||
location: 'Werkstatt A · Werkzeugwand',
|
||||
category: 'hand',
|
||||
trainingRequired: false,
|
||||
homeAllowed: true,
|
||||
maxRentalDays: 7,
|
||||
emoji: '🔨',
|
||||
},
|
||||
{
|
||||
id: 't4',
|
||||
name: 'Lötstation Weller WE 1010',
|
||||
description:
|
||||
'Digitale Lötstation 70 W mit Temperaturregelung 100-450 °C. Inkl. Lötspitzen-Set und Entlötlitze.',
|
||||
location: 'Labor 2 · Tisch 3',
|
||||
category: 'electronics',
|
||||
trainingRequired: true,
|
||||
requiredTrainingId: 'soldering',
|
||||
homeAllowed: false,
|
||||
maxRentalDays: 2,
|
||||
emoji: '🔌',
|
||||
},
|
||||
{
|
||||
id: 't5',
|
||||
name: 'Akku-Schrauber Makita 18V',
|
||||
description:
|
||||
'Kompakter Akku-Schrauber mit 18 V Lithium-Ionen-Akku. Inklusive Bit-Set und Ersatzakku.',
|
||||
location: 'Werkstatt A · Schrank 1',
|
||||
category: 'power',
|
||||
trainingRequired: false,
|
||||
homeAllowed: true,
|
||||
maxRentalDays: 5,
|
||||
emoji: '🔧',
|
||||
},
|
||||
{
|
||||
id: 't6',
|
||||
name: 'Laser-Schneider K40',
|
||||
description:
|
||||
'CO2-Laserschneider 40 W für Holz, Acryl und Pappe. Arbeitsfläche 300×200 mm. Achtung: Materialliste beachten!',
|
||||
location: 'Labor 1 · Standalone',
|
||||
category: 'workshop',
|
||||
trainingRequired: true,
|
||||
requiredTrainingId: 'laser',
|
||||
homeAllowed: false,
|
||||
maxRentalDays: 1,
|
||||
emoji: '⚡',
|
||||
},
|
||||
{
|
||||
id: 't7',
|
||||
name: 'Digital-Multimeter Fluke 117',
|
||||
description:
|
||||
'Profi-Multimeter mit AutoVolt, True-RMS und kontaktloser Spannungsprüfung. Für Elektronik-Diagnose.',
|
||||
location: 'Labor 2 · Werkzeugkoffer',
|
||||
category: 'measurement',
|
||||
trainingRequired: false,
|
||||
homeAllowed: true,
|
||||
maxRentalDays: 3,
|
||||
emoji: '🔋',
|
||||
},
|
||||
{
|
||||
id: 't8',
|
||||
name: 'Nähmaschine Bernina B215',
|
||||
description:
|
||||
'Robuste Nähmaschine für Anfänger und Fortgeschrittene. 18 Nähstiche, automatisches Knopfloch.',
|
||||
location: 'Textil-Ecke · Tisch 1',
|
||||
category: 'sewing',
|
||||
trainingRequired: false,
|
||||
homeAllowed: false,
|
||||
maxRentalDays: 2,
|
||||
emoji: '🧵',
|
||||
},
|
||||
{
|
||||
id: 't9',
|
||||
name: 'Fahrrad-Montageständer Park Tool',
|
||||
description:
|
||||
'Professioneller Werkstatt-Montageständer für alle Fahrrad-Reparaturen. Tragkraft bis 36 kg.',
|
||||
location: 'Fahrrad-Werkstatt · Eingang',
|
||||
category: 'bike',
|
||||
trainingRequired: false,
|
||||
homeAllowed: true,
|
||||
maxRentalDays: 5,
|
||||
emoji: '🚲',
|
||||
},
|
||||
{
|
||||
id: 't10',
|
||||
name: 'Akku-Heckenschere Bosch',
|
||||
description:
|
||||
'Akku-Heckenschere AHS 50-20 LI mit 50 cm Schwertlänge. Für Hecken bis 20 mm Astdurchmesser.',
|
||||
location: 'Garten-Schuppen',
|
||||
category: 'garden',
|
||||
trainingRequired: false,
|
||||
homeAllowed: true,
|
||||
maxRentalDays: 4,
|
||||
emoji: '🌿',
|
||||
},
|
||||
{
|
||||
id: 't11',
|
||||
name: 'Oszilloskop Rigol DS1054Z',
|
||||
description:
|
||||
'4-Kanal-Oszilloskop 50 MHz, 1 GSa/s Abtastrate. Für anspruchsvolle Elektronik-Messungen.',
|
||||
location: 'Labor 2 · Tisch 2',
|
||||
category: 'measurement',
|
||||
trainingRequired: true,
|
||||
requiredTrainingId: 'soldering',
|
||||
homeAllowed: false,
|
||||
maxRentalDays: 2,
|
||||
emoji: '📊',
|
||||
},
|
||||
{
|
||||
id: 't12',
|
||||
name: 'CNC-Fräse Stepcraft D.420',
|
||||
description:
|
||||
'Tisch-CNC-Fräse mit Arbeitsbereich 420×300×140 mm. Für Holz, Aluminium und Kunststoff.',
|
||||
location: 'Labor 1 · CNC-Bereich',
|
||||
category: 'workshop',
|
||||
trainingRequired: true,
|
||||
requiredTrainingId: 'cnc',
|
||||
homeAllowed: false,
|
||||
maxRentalDays: 1,
|
||||
emoji: '⚙️',
|
||||
},
|
||||
];
|
||||
|
||||
export const MOCK_MANUALS: Manual[] = [
|
||||
{
|
||||
toolId: 't1',
|
||||
title: 'Bohrmaschine richtig nutzen',
|
||||
content:
|
||||
'Spannfutter gegen den Uhrzeigersinn lösen, passenden Bohrer einsetzen, fest zudrehen. Bei Beton den Schlag-Modus aktivieren. Augenschutz tragen!',
|
||||
},
|
||||
{
|
||||
toolId: 't2',
|
||||
title: 'Prusa i3 vorbereiten',
|
||||
content:
|
||||
'Druckbett mit Isopropanol reinigen, Filament laden, Erstschicht-Kalibrierung durchführen. Erste 10 Minuten am Drucker bleiben.',
|
||||
},
|
||||
{
|
||||
toolId: 't4',
|
||||
title: 'Löten — Grundlagen',
|
||||
content:
|
||||
'Lötkolben auf 320-360 °C aufheizen. Erst Pad und Bauteil erhitzen, dann Lötzinn zuführen. Niemals direkt auf den Lötkolben löten — nur indirekt über Pad/Pin.',
|
||||
},
|
||||
{
|
||||
toolId: 't6',
|
||||
title: 'Laser-Cutter Sicherheit',
|
||||
content:
|
||||
'NIEMALS verbotene Materialien (PVC, Vinyl, Polycarbonat) schneiden — Chlorgas! Absaugung VOR Start prüfen. Während des Schnitts nie unbeaufsichtigt lassen.',
|
||||
},
|
||||
{
|
||||
toolId: 't8',
|
||||
title: 'Nähmaschine einfädeln',
|
||||
content:
|
||||
'Oberfaden vom Garnrollenhalter durch alle markierten Führungen. Spule unten einsetzen — Faden gegen den Uhrzeigersinn. Probelauf an Stoffrest.',
|
||||
},
|
||||
{
|
||||
toolId: 't11',
|
||||
title: 'Oszilloskop-Trigger',
|
||||
content:
|
||||
'Trigger immer auf das stabilste Signal setzen. AutoSetup gibt einen guten Startpunkt. Pegel manuell anpassen, bis das Bild "steht".',
|
||||
},
|
||||
];
|
||||
|
||||
export const MOCK_TRAININGS = [
|
||||
{ id: 'drill', name: 'Bohrmaschinen-Einweisung', duration: '30 min' },
|
||||
{ id: '3d-print', name: '3D-Druck Grundlagen', duration: '90 min' },
|
||||
{ id: 'laser', name: 'Laser-Cutter Sicherheit', duration: '60 min' },
|
||||
{ id: 'cnc', name: 'CNC-Fräse Einführung', duration: '120 min' },
|
||||
{ id: 'soldering', name: 'Löten für Anfänger', duration: '45 min' },
|
||||
];
|
||||
@@ -0,0 +1,90 @@
|
||||
'use client';
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
import { Reservation } from '../types';
|
||||
|
||||
interface ReservationState {
|
||||
reservations: Reservation[];
|
||||
addReservation: (reservation: Omit<Reservation, 'id' | 'status'>) => string;
|
||||
updateStatus: (id: string, status: Reservation['status']) => void;
|
||||
removeReservation: (id: string) => void;
|
||||
}
|
||||
|
||||
function randomId() {
|
||||
if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
return Math.random().toString(36).slice(2, 11);
|
||||
}
|
||||
|
||||
const isoOffset = (offsetDays: number) =>
|
||||
new Date(Date.now() + offsetDays * 86_400_000).toISOString().split('T')[0];
|
||||
|
||||
const INITIAL_RESERVATIONS: Reservation[] = [
|
||||
{
|
||||
id: 'res1',
|
||||
userId: '1',
|
||||
toolId: 't1',
|
||||
startDate: isoOffset(0),
|
||||
endDate: isoOffset(2),
|
||||
status: 'approved',
|
||||
},
|
||||
{
|
||||
id: 'res2',
|
||||
userId: '2',
|
||||
toolId: 't2',
|
||||
startDate: isoOffset(1),
|
||||
endDate: isoOffset(3),
|
||||
status: 'requested',
|
||||
},
|
||||
{
|
||||
id: 'res3',
|
||||
userId: '3',
|
||||
toolId: 't4',
|
||||
startDate: isoOffset(-1),
|
||||
endDate: isoOffset(1),
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
id: 'res4',
|
||||
userId: '1',
|
||||
toolId: 't6',
|
||||
startDate: isoOffset(5),
|
||||
endDate: isoOffset(6),
|
||||
status: 'requested',
|
||||
},
|
||||
];
|
||||
|
||||
export const useReservationStore = create<ReservationState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
reservations: INITIAL_RESERVATIONS,
|
||||
addReservation: (res) => {
|
||||
const id = randomId();
|
||||
const newReservation: Reservation = {
|
||||
...res,
|
||||
id,
|
||||
status: 'requested',
|
||||
};
|
||||
set((state) => ({ reservations: [...state.reservations, newReservation] }));
|
||||
return id;
|
||||
},
|
||||
updateStatus: (id, status) =>
|
||||
set((state) => ({
|
||||
reservations: state.reservations.map((r) =>
|
||||
r.id === id ? { ...r, status } : r
|
||||
),
|
||||
})),
|
||||
removeReservation: (id) =>
|
||||
set((state) => ({
|
||||
reservations: state.reservations.filter((r) => r.id !== id),
|
||||
})),
|
||||
}),
|
||||
{
|
||||
name: 'patchpilot:reservations',
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
version: 1,
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,58 @@
|
||||
'use client';
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
import type { Manual, Tool } from '../types';
|
||||
import { MOCK_TOOLS, MOCK_MANUALS } from './mock-data';
|
||||
|
||||
interface ToolsState {
|
||||
tools: Tool[];
|
||||
manuals: Manual[];
|
||||
addTool: (tool: Omit<Tool, 'id'>) => string;
|
||||
updateTool: (id: string, patch: Partial<Tool>) => void;
|
||||
removeTool: (id: string) => void;
|
||||
addManual: (manual: Manual) => void;
|
||||
removeManual: (toolId: string, idx: number) => void;
|
||||
resetToMockData: () => void;
|
||||
}
|
||||
|
||||
function randomId() {
|
||||
if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
return Math.random().toString(36).slice(2, 11);
|
||||
}
|
||||
|
||||
export const useToolsStore = create<ToolsState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
tools: MOCK_TOOLS,
|
||||
manuals: MOCK_MANUALS,
|
||||
addTool: (tool) => {
|
||||
const id = `t${randomId()}`;
|
||||
set((state) => ({ tools: [...state.tools, { ...tool, id }] }));
|
||||
return id;
|
||||
},
|
||||
updateTool: (id, patch) =>
|
||||
set((state) => ({
|
||||
tools: state.tools.map((t) => (t.id === id ? { ...t, ...patch } : t)),
|
||||
})),
|
||||
removeTool: (id) =>
|
||||
set((state) => ({ tools: state.tools.filter((t) => t.id !== id) })),
|
||||
addManual: (manual) =>
|
||||
set((state) => ({ manuals: [...state.manuals, manual] })),
|
||||
removeManual: (toolId, idx) =>
|
||||
set((state) => ({
|
||||
manuals: state.manuals.filter(
|
||||
(m, i) => !(m.toolId === toolId && i === idx)
|
||||
),
|
||||
})),
|
||||
resetToMockData: () => set({ tools: MOCK_TOOLS, manuals: MOCK_MANUALS }),
|
||||
}),
|
||||
{
|
||||
name: 'patchpilot:tools',
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
version: 1,
|
||||
}
|
||||
)
|
||||
);
|
||||
Reference in New Issue
Block a user