feat: Bottle Split System (Flaschenteilung)

- Add bottle_splits and split_participants tables with RLS
- Implement soft-lock: pending requests count as reserved
- Create /splits/create wizard (3 steps: bottle, pricing, shipping)
- Create /splits/[slug] public page with price calculator
- Create /splits/manage host dashboard with participant workflow
- Add SplitProgressBar component for visual volume display
- Status workflow: PENDING -> APPROVED -> PAID -> SHIPPED
- Forum export (BBCode format)
- Saved defaults in localStorage for glass/shipping costs
This commit is contained in:
2025-12-25 22:36:38 +01:00
parent 75461d7c30
commit 0c7786db90
7 changed files with 1871 additions and 1 deletions

View File

@@ -0,0 +1,101 @@
'use client';
import React from 'react';
interface SplitProgressBarProps {
totalVolume: number;
hostShare: number;
taken: number;
reserved: number;
showLabels?: boolean;
height?: 'sm' | 'md' | 'lg';
}
export default function SplitProgressBar({
totalVolume,
hostShare,
taken,
reserved,
showLabels = true,
height = 'md',
}: SplitProgressBarProps) {
const available = totalVolume - hostShare - taken - reserved;
const hostPercent = (hostShare / totalVolume) * 100;
const takenPercent = (taken / totalVolume) * 100;
const reservedPercent = (reserved / totalVolume) * 100;
const availablePercent = (available / totalVolume) * 100;
const heightClass = height === 'sm' ? 'h-3' : height === 'lg' ? 'h-8' : 'h-5';
return (
<div className="space-y-2">
<div className={`w-full ${heightClass} rounded-full overflow-hidden flex bg-zinc-800`}>
{/* Host Share - Grey */}
{hostPercent > 0 && (
<div
className="bg-zinc-600 flex items-center justify-center text-[8px] font-bold text-white/70"
style={{ width: `${hostPercent}%` }}
>
{height !== 'sm' && hostPercent > 10 && 'Host'}
</div>
)}
{/* Taken - Orange/Red */}
{takenPercent > 0 && (
<div
className="bg-orange-600 flex items-center justify-center text-[8px] font-bold text-white"
style={{ width: `${takenPercent}%` }}
>
{height !== 'sm' && takenPercent > 8 && `${taken}cl`}
</div>
)}
{/* Reserved/Pending - Yellow */}
{reservedPercent > 0 && (
<div
className="bg-yellow-500 flex items-center justify-center text-[8px] font-bold text-black/70"
style={{ width: `${reservedPercent}%` }}
>
{height !== 'sm' && reservedPercent > 8 && `${reserved}cl`}
</div>
)}
{/* Available - Green */}
{availablePercent > 0 && (
<div
className="bg-green-500 flex items-center justify-center text-[8px] font-bold text-white"
style={{ width: `${availablePercent}%` }}
>
{height !== 'sm' && availablePercent > 8 && `${available}cl`}
</div>
)}
</div>
{showLabels && (
<div className="flex flex-wrap gap-3 text-[10px] font-bold">
<div className="flex items-center gap-1.5">
<div className="w-2.5 h-2.5 rounded-sm bg-zinc-600" />
<span className="text-zinc-500">Host ({hostShare}cl)</span>
</div>
{taken > 0 && (
<div className="flex items-center gap-1.5">
<div className="w-2.5 h-2.5 rounded-sm bg-orange-600" />
<span className="text-zinc-500">Vergeben ({taken}cl)</span>
</div>
)}
{reserved > 0 && (
<div className="flex items-center gap-1.5">
<div className="w-2.5 h-2.5 rounded-sm bg-yellow-500" />
<span className="text-zinc-500">Reserviert ({reserved}cl)</span>
</div>
)}
<div className="flex items-center gap-1.5">
<div className="w-2.5 h-2.5 rounded-sm bg-green-500" />
<span className="text-zinc-500">Verfügbar ({available}cl)</span>
</div>
</div>
)}
</div>
);
}