feat(my-customers): flatten orders into per-device cards

- split orders by device_name into FlattenedDevice entries
- strip device prefixes from product and module names
- render one DeviceCard per Kasse in the customer accordion
- add targeted upgrade URL with orderId, customer_id, device_id
- wizard starts at step 3 in upgrade mode with locked customer
- mark existing licensed modules as read-only with badge
This commit is contained in:
DanielS
2026-07-23 09:45:14 +02:00
parent 086a893e19
commit 0e4495292d
7 changed files with 369 additions and 156 deletions

View File

@@ -74,6 +74,9 @@ export function OrderWizard({
initialOrder,
isAdmin = false,
companies = [],
upgradeMode = false,
initialEndCustomerId = null,
lockedDeviceId = null,
}: {
products: Product[]
categories: Category[]
@@ -82,9 +85,13 @@ export function OrderWizard({
initialOrder?: Order | null
isAdmin?: boolean
companies?: any[]
upgradeMode?: boolean
initialEndCustomerId?: string | null
lockedDeviceId?: string | null
}) {
const router = useRouter()
const [step, setStep] = useState(initialOrder ? 3 : 1)
// Upgrade-Mode: starte direkt bei Schritt 3 (Software)
const [step, setStep] = useState(upgradeMode ? 3 : (initialOrder ? 3 : 1))
const [isSubmitting, setIsSubmitting] = useState(false)
const [selectedCompanyId, setSelectedCompanyId] = useState<string | null>(
initialOrder?.company_id ?? (isAdmin ? 'all' : null)
@@ -138,7 +145,26 @@ export function OrderWizard({
}
})
})
const [deviceName, setDeviceName] = useState<string>('')
// Upgrade-Mode: Ermittle bereits lizenzierte Modul-IDs für die Ziel-Kasse
const existingModuleIds = useMemo<string[]>(() => {
if (!upgradeMode || !initialOrder || !lockedDeviceId) return []
const items = initialOrder.order_data?.items || []
const deviceItems = items.filter(
(item: any) => (item.device_name || 'Kasse 1') === lockedDeviceId
)
const moduleIds: string[] = []
deviceItems.forEach((item: any) => {
item.selected_modules?.forEach((mod: any) => {
if (mod.module_id) moduleIds.push(mod.module_id)
})
})
return moduleIds
}, [upgradeMode, initialOrder, lockedDeviceId])
const [deviceName, setDeviceName] = useState<string>(() => {
if (upgradeMode && lockedDeviceId) return `${lockedDeviceId} Upgrade`
return ''
})
const [editingIdx, setEditingIdx] = useState<number | null>(null)
const [toast, setToast] = useState<{ message: string; type: 'error' | 'success' } | null>(null)
@@ -154,11 +180,12 @@ export function OrderWizard({
// Endkunden-State
const [endCustomers, setEndCustomers] = useState<EndCustomer[]>(initialEndCustomers)
const [selectedEndCustomerId, setSelectedEndCustomerId] = useState<string | null>(
initialOrder
? initialOrder.end_customer_id
: (initialEndCustomers.length > 0 ? initialEndCustomers[0].id : null)
)
const [selectedEndCustomerId, setSelectedEndCustomerId] = useState<string | null>(() => {
// Upgrade-Mode: Kundenauswahl aus URL-Param
if (upgradeMode && initialEndCustomerId) return initialEndCustomerId
if (initialOrder) return initialOrder.end_customer_id
return initialEndCustomers.length > 0 ? initialEndCustomers[0].id : null
})
const selectedEndCustomer = endCustomers.find(c => c.id === selectedEndCustomerId) ?? null
const [searchTerm, setSearchTerm] = useState('')
@@ -751,6 +778,17 @@ export function OrderWizard({
<ProgressStepper step={step} basketItemsCount={basketItems.length} />
</div>
{/* Upgrade-Modus Hinweis-Banner */}
{upgradeMode && lockedDeviceId && (
<div className="mb-4 p-3 rounded-xl bg-primary/10 border border-primary/20 text-xs text-primary/90 flex items-start gap-2">
<span className="text-primary mt-0.5"></span>
<div>
<p className="font-semibold">Upgrade-Modus: {lockedDeviceId}</p>
<p className="text-primary/70 mt-0.5">Bereits lizenzierte Module sind ausgegraut und können nicht doppelt gebucht werden.</p>
</div>
</div>
)}
{/* Scrollable category content */}
<StepSoftware
visibleCategories={visibleCategories}
@@ -765,6 +803,7 @@ export function OrderWizard({
selectedBillingInterval={selectedBillingInterval}
billingLabel={billingLabel}
billingBadgeClass={billingBadgeClass}
existingModuleIds={existingModuleIds}
/>
</div>