feat: complete ShiftCraft — AI-powered shift scheduling SaaS

Complete implementation including:
- Landing page with hero, features, how-it-works, pricing
- Employee management (CRUD with soft delete)
- AI constraint parser (Anthropic Claude API)
- German labor law templates (ArbZG §3, §5, §9)
- HiGHS ILP solver for optimal fair schedules
- Schedule calendar result view (employee × date grid)
- Shift framework configuration (periods + shifts)
- Subscription tiers: Free / Pro / Business
- PocketBase setup script with collection creation + seed data
- .env.example with all required variables documented

Pages: employees, constraints (list/new/templates), schedules (list/new/[id]),
       settings (organization/shifts/billing), dashboard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-18 07:47:31 +02:00
parent 2ea4ca5d52
commit 36e0946ee4
38 changed files with 4254 additions and 133 deletions

View File

@@ -1,41 +1,272 @@
<template>
<div>
<UPageHero
:title="$t('hero.title')"
:description="$t('hero.description')"
:links="links"
/>
<div class="flex justify-center mt-8">
<CounterWidget v-if="isAuthenticated" />
<UEmpty
v-else
icon="i-lucide-user-x"
:title="$t('counter.notAuthenticated')"
:description="$t('counter.signInToUse')"
/>
</div>
<div class="min-h-screen bg-white">
<!-- Navigation -->
<nav class="fixed top-0 w-full z-50 bg-white/80 backdrop-blur-xl border-b border-gray-100">
<div class="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
<div class="flex items-center gap-2">
<div class="w-8 h-8 rounded-xl bg-gradient-to-br from-indigo-500 to-violet-600 flex items-center justify-center">
<span class="text-white font-bold text-sm">S</span>
</div>
<span class="font-bold text-gray-900 text-lg">ShiftCraft</span>
</div>
<div class="hidden md:flex items-center gap-8">
<a href="#features" class="text-gray-600 hover:text-gray-900 text-sm font-medium transition-colors">Features</a>
<a href="#how-it-works" class="text-gray-600 hover:text-gray-900 text-sm font-medium transition-colors">So funktioniert's</a>
<a href="#pricing" class="text-gray-600 hover:text-gray-900 text-sm font-medium transition-colors">Preise</a>
</div>
<div class="flex items-center gap-3">
<NuxtLink to="/login" class="text-sm font-medium text-gray-700 hover:text-gray-900 transition-colors">Anmelden</NuxtLink>
<NuxtLink to="/register">
<UButton color="primary" size="sm">Kostenlos starten</UButton>
</NuxtLink>
</div>
</div>
</nav>
<!-- Hero -->
<section class="pt-32 pb-20 px-6 relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-indigo-50 via-violet-50 to-emerald-50 -z-10" />
<div class="absolute top-20 right-0 w-96 h-96 bg-violet-200 rounded-full filter blur-3xl opacity-30 -z-10" />
<div class="absolute bottom-0 left-20 w-64 h-64 bg-indigo-200 rounded-full filter blur-3xl opacity-30 -z-10" />
<div class="max-w-4xl mx-auto text-center">
<div class="inline-flex items-center gap-2 px-4 py-1.5 bg-indigo-50 text-indigo-700 rounded-full text-sm font-medium mb-8 border border-indigo-100">
<span class="w-1.5 h-1.5 rounded-full bg-indigo-500 animate-pulse" />
Schichtplanung neu gedacht
</div>
<h1 class="text-5xl md:text-7xl font-extrabold text-gray-900 leading-tight mb-6">
Schichtplanung,<br>
<span class="bg-gradient-to-r from-indigo-600 to-violet-600 bg-clip-text text-transparent">
die Ihre Sprache<br>spricht
</span>
</h1>
<p class="text-xl text-gray-600 max-w-2xl mx-auto mb-10 leading-relaxed">
Einfach Ihre Wünsche und Regeln eingeben — wie in einer E-Mail.
ShiftCraft erstellt automatisch den optimalen, fairen Schichtplan.
</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center mb-16">
<NuxtLink to="/register">
<UButton color="primary" size="xl" trailing-icon="i-lucide-arrow-right" class="shadow-lg shadow-indigo-200">
Kostenlos starten
</UButton>
</NuxtLink>
<a href="#how-it-works">
<UButton color="neutral" variant="outline" size="xl">
Demo ansehen
</UButton>
</a>
</div>
<!-- Mock UI Preview -->
<div class="relative max-w-3xl mx-auto">
<div class="bg-white rounded-3xl shadow-2xl border border-gray-100 overflow-hidden">
<div class="bg-gray-50 px-6 py-4 border-b border-gray-100 flex items-center gap-2">
<div class="w-3 h-3 rounded-full bg-red-400" />
<div class="w-3 h-3 rounded-full bg-yellow-400" />
<div class="w-3 h-3 rounded-full bg-green-400" />
<span class="ml-4 text-xs text-gray-400 font-medium">ShiftCraft — Neue Bedingung</span>
</div>
<div class="p-6 text-left">
<div class="mb-4">
<label class="text-xs font-medium text-gray-500 uppercase tracking-wide mb-2 block">Bedingung eingeben</label>
<div class="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-gray-700 text-sm min-h-[80px]">
Sabine mag keine Nachtschichten und sollte maximal 4 Tage am Stück arbeiten. Tom darf nicht mehr als 3 Nachtschichten hintereinander machen.
</div>
</div>
<div class="space-y-2">
<div v-for="preview in constraintPreviews" :key="preview.text" class="bg-indigo-50 border border-indigo-100 rounded-xl p-3 flex items-start gap-3">
<UIcon name="i-lucide-check-circle" class="text-indigo-600 mt-0.5 w-4 h-4 shrink-0" />
<div>
<p class="text-sm font-medium text-indigo-900">{{ preview.text }}</p>
<p class="text-xs text-indigo-500">{{ preview.meta }}</p>
</div>
</div>
</div>
</div>
</div>
<div class="absolute -bottom-4 -right-4 w-32 h-32 bg-violet-100 rounded-full -z-10" />
<div class="absolute -top-4 -left-4 w-24 h-24 bg-emerald-100 rounded-full -z-10" />
</div>
</div>
</section>
<!-- Features -->
<section id="features" class="py-24 px-6">
<div class="max-w-6xl mx-auto">
<div class="text-center mb-16">
<h2 class="text-4xl font-bold text-gray-900 mb-4">Alles was Sie brauchen</h2>
<p class="text-xl text-gray-600 max-w-2xl mx-auto">Keine komplizierte Software mehr. Einfach eingeben, was Sie wollen.</p>
</div>
<div class="grid md:grid-cols-3 gap-8">
<div v-for="feature in features" :key="feature.title" class="bg-white rounded-2xl border border-gray-100 shadow-sm p-8 hover:shadow-md transition-shadow">
<div class="w-12 h-12 rounded-2xl flex items-center justify-center text-2xl mb-6" :class="feature.bgColor">
{{ feature.icon }}
</div>
<h3 class="text-xl font-bold text-gray-900 mb-3">{{ feature.title }}</h3>
<p class="text-gray-600 leading-relaxed">{{ feature.description }}</p>
</div>
</div>
</div>
</section>
<!-- How it works -->
<section id="how-it-works" class="py-24 px-6 bg-gradient-to-br from-gray-50 to-indigo-50/30">
<div class="max-w-5xl mx-auto">
<div class="text-center mb-16">
<h2 class="text-4xl font-bold text-gray-900 mb-4">So einfach geht's</h2>
<p class="text-xl text-gray-600">In drei Schritten zum perfekten Schichtplan</p>
</div>
<div class="grid md:grid-cols-3 gap-8">
<div v-for="(step, i) in steps" :key="step.title" class="text-center relative">
<div class="w-20 h-20 rounded-3xl bg-white shadow-lg border border-gray-100 flex items-center justify-center text-4xl mx-auto mb-6 relative">
{{ step.icon }}
<span class="absolute -top-2 -right-2 w-7 h-7 rounded-full bg-indigo-600 text-white text-xs font-bold flex items-center justify-center">{{ i + 1 }}</span>
</div>
<h3 class="text-xl font-bold text-gray-900 mb-3">{{ step.title }}</h3>
<p class="text-gray-600 leading-relaxed">{{ step.description }}</p>
</div>
</div>
</div>
</section>
<!-- Legal highlight -->
<section class="py-24 px-6">
<div class="max-w-6xl mx-auto">
<div class="bg-gradient-to-r from-indigo-600 to-violet-700 rounded-3xl p-12 text-white">
<div class="grid md:grid-cols-2 gap-12 items-center">
<div>
<div class="inline-flex items-center gap-2 px-4 py-1.5 bg-white/20 rounded-full text-sm font-medium mb-6">
Rechtlich abgesichert
</div>
<h2 class="text-3xl font-bold mb-4">Gesetzliche Vorgaben inklusive</h2>
<p class="text-indigo-100 text-lg leading-relaxed mb-6">
Alle relevanten Vorschriften aus dem Arbeitszeitgesetz sind bereits als Vorlagen hinterlegt.
Einfach aktivieren fertig.
</p>
<ul class="space-y-3">
<li v-for="rule in legalRules" :key="rule" class="flex items-center gap-3">
<span class="text-emerald-300"></span>
<span class="text-indigo-100">{{ rule }}</span>
</li>
</ul>
</div>
<div class="space-y-3">
<div v-for="template in legalTemplates" :key="template.label" class="bg-white/10 backdrop-blur rounded-2xl p-4 border border-white/20">
<div class="flex items-center justify-between mb-1">
<span class="font-medium">{{ template.label }}</span>
<span class="text-xs bg-red-400/30 text-red-200 px-2 py-0.5 rounded-full">Pflicht</span>
</div>
<p class="text-sm text-indigo-200">{{ template.description }}</p>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Pricing -->
<section id="pricing" class="py-24 px-6 bg-gray-50">
<div class="max-w-6xl mx-auto">
<div class="text-center mb-16">
<h2 class="text-4xl font-bold text-gray-900 mb-4">Einfache Preise</h2>
<p class="text-xl text-gray-600">Kein verstecktes Kleingedrucktes. Starten Sie kostenlos.</p>
</div>
<div class="grid md:grid-cols-3 gap-8">
<div
v-for="(tier, key) in PLAN_LIMITS"
:key="key"
class="bg-white rounded-2xl border border-gray-100 shadow-sm p-8 relative"
:class="key === 'pro' ? 'ring-2 ring-indigo-500 shadow-xl shadow-indigo-100' : ''"
>
<div v-if="key === 'pro'" class="absolute -top-4 left-1/2 -translate-x-1/2 px-4 py-1 bg-indigo-600 text-white text-sm font-medium rounded-full whitespace-nowrap">
Beliebteste Wahl
</div>
<h3 class="text-2xl font-bold text-gray-900 mb-2">{{ tier.name }}</h3>
<p class="text-gray-500 mb-6 text-sm">{{ tier.description }}</p>
<div class="mb-8">
<span class="text-5xl font-extrabold text-gray-900">{{ tier.price_eur_month }}</span>
<span class="text-gray-500 ml-2">/Monat</span>
</div>
<ul class="space-y-3 mb-8">
<li v-for="f in tier.features" :key="f" class="flex items-center gap-3 text-gray-600 text-sm">
<UIcon name="i-lucide-check" class="text-emerald-500 w-4 h-4 shrink-0" />
{{ f }}
</li>
</ul>
<NuxtLink :to="tier.price_eur_month === 0 ? '/register' : '/register'">
<UButton
:color="key === 'pro' ? 'primary' : 'neutral'"
:variant="key === 'pro' ? 'solid' : 'outline'"
class="w-full justify-center"
size="lg"
>
{{ tier.price_eur_month === 0 ? 'Kostenlos starten' : 'Jetzt upgraden' }}
</UButton>
</NuxtLink>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="py-12 px-6 border-t border-gray-100">
<div class="max-w-6xl mx-auto flex flex-col md:flex-row items-center justify-between gap-4">
<div class="flex items-center gap-2">
<div class="w-7 h-7 rounded-lg bg-gradient-to-br from-indigo-500 to-violet-600 flex items-center justify-center">
<span class="text-white font-bold text-xs">S</span>
</div>
<span class="font-bold text-gray-900">ShiftCraft</span>
</div>
<p class="text-gray-500 text-sm">© 2025 ShiftCraft. Alle Rechte vorbehalten.</p>
<div class="flex gap-6">
<a href="#" class="text-gray-500 hover:text-gray-700 text-sm transition-colors">Datenschutz</a>
<a href="#" class="text-gray-500 hover:text-gray-700 text-sm transition-colors">Impressum</a>
<a href="#" class="text-gray-500 hover:text-gray-700 text-sm transition-colors">Kontakt</a>
</div>
</div>
</footer>
</div>
</template>
<script setup lang="ts">
import type { ButtonProps } from '@nuxt/ui'
import { PLAN_LIMITS } from '~/app/utils/planLimits'
const { isAuthenticated } = storeToRefs(useUser())
definePageMeta({ layout: false })
const { t } = useI18n()
const constraintPreviews = [
{ text: 'Sabine bevorzugt keine Nachtschichten', meta: 'Soft-Bedingung · Gewicht 65' },
{ text: 'Max. 4 aufeinanderfolgende Schichten (Sabine)', meta: 'Harte Bedingung' },
{ text: 'Max. 3 Nachtschichten am Stück (Tom)', meta: 'Harte Bedingung' },
]
const links = computed(() => [
{
label: t('hero.signUp'),
to: '/login',
icon: 'i-lucide-log-in'
},
{
label: t('hero.profile'),
to: '/profile',
color: 'neutral',
variant: 'subtle',
trailingIcon: 'i-lucide-arrow-right'
}
] as ButtonProps[])
const features = [
{ icon: '💬', title: 'Einfach eingeben', description: 'Keine komplizierten Formulare. Schreiben Sie einfach, was Sie wollen — wie in einer Nachricht.', bgColor: 'bg-indigo-50' },
{ icon: '⚡', title: 'Automatisch optimiert', description: 'Unser intelligentes System erstellt den bestmöglichen, fairen Plan in Sekunden — nicht Stunden.', bgColor: 'bg-violet-50' },
{ icon: '⚖️', title: 'Rechtlich sicher', description: 'Gesetzliche Vorlagen für Deutschland, Österreich und die Schweiz sind bereits eingebaut.', bgColor: 'bg-emerald-50' },
{ icon: '🤝', title: 'Mitarbeiterwünsche', description: 'Individuelle Präferenzen werden berücksichtigt — für mehr Zufriedenheit im Team.', bgColor: 'bg-amber-50' },
{ icon: '📊', title: 'Faire Verteilung', description: 'Automatisch faire Verteilung von Nacht-, Wochenend- und Feiertagsschichten.', bgColor: 'bg-pink-50' },
{ icon: '📤', title: 'Export & Teilen', description: 'Den fertigen Plan als PDF oder Excel exportieren und direkt teilen.', bgColor: 'bg-cyan-50' },
]
const steps = [
{ icon: '🗓️', title: 'Rahmen festlegen', description: 'Definieren Sie Ihre Schichtzeiten und wie viele Mitarbeiter pro Schicht benötigt werden.' },
{ icon: '✏️', title: 'Regeln eingeben', description: 'Schreiben Sie Ihre Wünsche und Regeln in normaler Sprache — so einfach wie eine E-Mail.' },
{ icon: '✨', title: 'Plan erhalten', description: 'ShiftCraft berechnet automatisch den optimalen, fairen Schichtplan für Ihr Team.' },
]
const legalRules = [
'Max. 10 Stunden Arbeitszeit pro Tag',
'Min. 11 Stunden Ruhezeit zwischen Schichten',
'Max. 48 Stunden Arbeitszeit pro Woche',
'Keine Frühschicht nach Nachtschicht',
]
const legalTemplates = [
{ label: 'Max. 10 Stunden/Tag', description: 'Arbeitszeitgesetz §3 — Pflicht für alle Unternehmen' },
{ label: 'Min. 11h Ruhezeit', description: 'Arbeitszeitgesetz §5 — Zwischen zwei Schichten' },
{ label: 'Max. 6 Tage am Stück', description: 'Arbeitszeitgesetz §9 — Sonntagsruhe' },
]
</script>