727 lines
26 KiB
TypeScript
727 lines
26 KiB
TypeScript
// frontend\src\pages\operations\CreateOperationModal.tsx
|
|
|
|
import { useEffect, useState, type FormEvent, type MouseEvent } from 'react'
|
|
import { DialogTitle } from '@headlessui/react'
|
|
import { XMarkIcon } from '@heroicons/react/24/outline'
|
|
import {
|
|
CheckIcon,
|
|
ChevronLeftIcon,
|
|
ChevronRightIcon,
|
|
} from '@heroicons/react/20/solid'
|
|
import Modal from '../../components/Modal'
|
|
import Button from '../../components/Button'
|
|
import AddressCombobox from '../../components/AddressCombobox'
|
|
import { formatOperationNumberInput, isCompleteOperationNumber } from '../../components/formatter'
|
|
|
|
type CreateOperationModalProps = {
|
|
open: boolean
|
|
setOpen: (open: boolean) => void
|
|
isSaving: boolean
|
|
error: string | null
|
|
onSubmit: (event: FormEvent<HTMLFormElement>) => void
|
|
}
|
|
|
|
type OperationFormValues = {
|
|
operationNumber: string
|
|
operationName: string
|
|
offense: string
|
|
caseWorker: string
|
|
targetObject: string
|
|
kwAddress: string
|
|
operationLeader: string
|
|
operationTeam: string
|
|
legend: string
|
|
camera: string
|
|
router: string
|
|
technology: string
|
|
switchbox: string
|
|
hardDrive: string
|
|
laptop: string
|
|
remark: string
|
|
}
|
|
|
|
type SetupStepId =
|
|
| 'masterData'
|
|
| 'addresses'
|
|
| 'responsibilities'
|
|
| 'notes'
|
|
| 'equipment'
|
|
| 'review'
|
|
|
|
const inputClassName =
|
|
'block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500'
|
|
|
|
const textareaClassName =
|
|
'block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500'
|
|
|
|
const initialFormValues: OperationFormValues = {
|
|
operationNumber: '',
|
|
operationName: '',
|
|
offense: '',
|
|
caseWorker: '',
|
|
targetObject: '',
|
|
kwAddress: '',
|
|
operationLeader: '',
|
|
operationTeam: '',
|
|
legend: '',
|
|
camera: '',
|
|
router: '',
|
|
technology: '',
|
|
switchbox: '',
|
|
hardDrive: '',
|
|
laptop: '',
|
|
remark: '',
|
|
}
|
|
|
|
const setupSteps: Array<{
|
|
id: SetupStepId
|
|
title: string
|
|
description: string
|
|
optional?: boolean
|
|
}> = [
|
|
{
|
|
id: 'masterData',
|
|
title: 'Stammdaten',
|
|
description: 'Einsatznummer, Name und Delikt.',
|
|
},
|
|
{
|
|
id: 'addresses',
|
|
title: 'Adressen',
|
|
description: 'KW-Adresse und Zielobjekt mit Kartenvorschau.',
|
|
optional: true,
|
|
},
|
|
{
|
|
id: 'responsibilities',
|
|
title: 'Zuständigkeiten',
|
|
description: 'Sachbearbeitung, Einsatzleitung und Einsatzteam.',
|
|
optional: true,
|
|
},
|
|
{
|
|
id: 'notes',
|
|
title: 'Zusatzinfos',
|
|
description: 'Legende und Bemerkung.',
|
|
optional: true,
|
|
},
|
|
{
|
|
id: 'equipment',
|
|
title: 'Technik',
|
|
description: 'Kamera, Router, Switchbox und Laptop.',
|
|
optional: true,
|
|
},
|
|
{
|
|
id: 'review',
|
|
title: 'Prüfen',
|
|
description: 'Daten prüfen und Einsatz speichern.',
|
|
},
|
|
]
|
|
|
|
function classNames(...classes: Array<string | false | null | undefined>) {
|
|
return classes.filter(Boolean).join(' ')
|
|
}
|
|
|
|
function getStepIndex(stepId: SetupStepId) {
|
|
return setupSteps.findIndex((step) => step.id === stepId)
|
|
}
|
|
|
|
function getDisplayValue(value: string) {
|
|
return value.trim() || '—'
|
|
}
|
|
|
|
function ReviewItem({
|
|
label,
|
|
value,
|
|
}: {
|
|
label: string
|
|
value: string
|
|
}) {
|
|
return (
|
|
<div className="min-w-0">
|
|
<dt className="text-xs font-medium text-gray-500 dark:text-gray-400">
|
|
{label}
|
|
</dt>
|
|
<dd className="mt-1 truncate text-sm text-gray-900 dark:text-white">
|
|
{getDisplayValue(value)}
|
|
</dd>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function StepHeader({
|
|
title,
|
|
description,
|
|
}: {
|
|
title: string
|
|
description: string
|
|
}) {
|
|
return (
|
|
<div>
|
|
<h3 className="text-base font-semibold text-gray-900 dark:text-white">
|
|
{title}
|
|
</h3>
|
|
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
|
{description}
|
|
</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default function CreateOperationModal({
|
|
open,
|
|
setOpen,
|
|
isSaving,
|
|
error,
|
|
onSubmit,
|
|
}: CreateOperationModalProps) {
|
|
const [activeStep, setActiveStep] = useState<SetupStepId>('masterData')
|
|
const [formValues, setFormValues] = useState<OperationFormValues>(initialFormValues)
|
|
const [localError, setLocalError] = useState<string | null>(null)
|
|
|
|
const activeStepIndex = getStepIndex(activeStep)
|
|
const isFirstStep = activeStepIndex === 0
|
|
const isLastStep = activeStepIndex === setupSteps.length - 1
|
|
|
|
useEffect(() => {
|
|
if (!open) {
|
|
return
|
|
}
|
|
|
|
setActiveStep('masterData')
|
|
setFormValues(initialFormValues)
|
|
setLocalError(null)
|
|
}, [open])
|
|
|
|
function handleOpenChange(nextOpen: boolean) {
|
|
if (!nextOpen && isSaving) {
|
|
return
|
|
}
|
|
|
|
setOpen(nextOpen)
|
|
}
|
|
|
|
function updateField(field: keyof OperationFormValues, value: string) {
|
|
const nextValue =
|
|
field === 'operationNumber'
|
|
? formatOperationNumberInput(value)
|
|
: value
|
|
|
|
setFormValues((currentValues) => ({
|
|
...currentValues,
|
|
[field]: nextValue,
|
|
}))
|
|
|
|
if (localError) {
|
|
setLocalError(null)
|
|
}
|
|
}
|
|
|
|
function goToStep(stepId: SetupStepId) {
|
|
setActiveStep(stepId)
|
|
}
|
|
|
|
function goToPreviousStep() {
|
|
if (isFirstStep) {
|
|
return
|
|
}
|
|
|
|
setActiveStep(setupSteps[activeStepIndex - 1].id)
|
|
}
|
|
|
|
function goToNextStep(event?: MouseEvent<HTMLButtonElement>) {
|
|
event?.preventDefault()
|
|
event?.stopPropagation()
|
|
|
|
if (isLastStep) {
|
|
return
|
|
}
|
|
|
|
if (activeStep === 'masterData') {
|
|
if (formValues.operationNumber.trim() === '') {
|
|
setLocalError('Bitte zuerst eine Einsatznummer eintragen.')
|
|
return
|
|
}
|
|
|
|
if (!isCompleteOperationNumber(formValues.operationNumber)) {
|
|
setLocalError('Die Einsatznummer muss das Format 000-00-0000 haben.')
|
|
return
|
|
}
|
|
}
|
|
|
|
setActiveStep(setupSteps[activeStepIndex + 1].id)
|
|
}
|
|
|
|
function skipStep() {
|
|
if (isLastStep) {
|
|
return
|
|
}
|
|
|
|
setActiveStep(setupSteps[activeStepIndex + 1].id)
|
|
}
|
|
|
|
function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
|
event.preventDefault()
|
|
|
|
if (activeStep !== 'review') {
|
|
goToNextStep()
|
|
return
|
|
}
|
|
|
|
if (formValues.operationNumber.trim() === '') {
|
|
setLocalError('Einsatznummer ist erforderlich.')
|
|
setActiveStep('masterData')
|
|
return
|
|
}
|
|
|
|
if (!isCompleteOperationNumber(formValues.operationNumber)) {
|
|
setLocalError('Die Einsatznummer muss das Format 000-00-0000 haben.')
|
|
setActiveStep('masterData')
|
|
return
|
|
}
|
|
|
|
onSubmit(event)
|
|
}
|
|
|
|
const combinedError = localError || error
|
|
|
|
return (
|
|
<Modal
|
|
open={open}
|
|
setOpen={handleOpenChange}
|
|
zIndexClassName="z-[9999]"
|
|
panelClassName="max-h-[calc(100dvh-2rem)] max-w-4xl bg-white dark:bg-gray-900"
|
|
>
|
|
<form
|
|
onSubmit={handleSubmit}
|
|
className="flex h-[calc(100dvh-2rem)] max-h-[calc(100dvh-2rem)] flex-col"
|
|
>
|
|
<input type="hidden" name="operationNumber" value={formValues.operationNumber} />
|
|
<input type="hidden" name="operationName" value={formValues.operationName} />
|
|
<input type="hidden" name="offense" value={formValues.offense} />
|
|
<input type="hidden" name="caseWorker" value={formValues.caseWorker} />
|
|
<input type="hidden" name="targetObject" value={formValues.targetObject} />
|
|
<input type="hidden" name="kwAddress" value={formValues.kwAddress} />
|
|
<input type="hidden" name="operationLeader" value={formValues.operationLeader} />
|
|
<input type="hidden" name="operationTeam" value={formValues.operationTeam} />
|
|
<input type="hidden" name="legend" value={formValues.legend} />
|
|
<input type="hidden" name="camera" value={formValues.camera} />
|
|
<input type="hidden" name="router" value={formValues.router} />
|
|
<input type="hidden" name="technology" value={formValues.technology} />
|
|
<input type="hidden" name="switchbox" value={formValues.switchbox} />
|
|
<input type="hidden" name="hardDrive" value={formValues.hardDrive} />
|
|
<input type="hidden" name="laptop" value={formValues.laptop} />
|
|
<input type="hidden" name="remark" value={formValues.remark} />
|
|
|
|
<div className="flex items-start justify-between border-b border-gray-200 bg-gray-100 px-4 py-4 sm:px-6 dark:border-white/10 dark:bg-gray-800">
|
|
<div>
|
|
<DialogTitle className="text-base font-semibold text-gray-900 dark:text-white">
|
|
Einsatz anlegen
|
|
</DialogTitle>
|
|
|
|
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
|
Folge den Schritten oder überspringe optionale Bereiche.
|
|
</p>
|
|
</div>
|
|
|
|
<Button
|
|
type="button"
|
|
variant="secondary"
|
|
color="gray"
|
|
size="sm"
|
|
onClick={() => handleOpenChange(false)}
|
|
disabled={isSaving}
|
|
>
|
|
<span className="sr-only">Schließen</span>
|
|
<XMarkIcon aria-hidden="true" className="size-5" />
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="border-b border-gray-200 px-4 py-4 sm:px-6 dark:border-white/10">
|
|
<nav aria-label="Einrichtungsschritte" className="grid grid-cols-2 gap-2 md:grid-cols-6">
|
|
{setupSteps.map((step, index) => {
|
|
const isActive = step.id === activeStep
|
|
const isDone = index < activeStepIndex
|
|
|
|
return (
|
|
<button
|
|
key={step.id}
|
|
type="button"
|
|
onClick={() => goToStep(step.id)}
|
|
className={classNames(
|
|
'rounded-lg border px-3 py-2 text-left transition',
|
|
isActive
|
|
? 'border-indigo-600 bg-indigo-50 text-indigo-700 dark:border-indigo-400 dark:bg-indigo-500/10 dark:text-indigo-300'
|
|
: isDone
|
|
? 'border-green-200 bg-green-50 text-green-700 dark:border-green-500/20 dark:bg-green-500/10 dark:text-green-300'
|
|
: 'border-gray-200 bg-white text-gray-600 hover:bg-gray-50 dark:border-white/10 dark:bg-white/5 dark:text-gray-300 dark:hover:bg-white/10',
|
|
)}
|
|
>
|
|
<span className="block text-[11px] font-medium">
|
|
Schritt {index + 1}
|
|
</span>
|
|
<span className="mt-0.5 block truncate text-sm font-semibold">
|
|
{step.title}
|
|
</span>
|
|
</button>
|
|
)
|
|
})}
|
|
</nav>
|
|
</div>
|
|
|
|
{combinedError && (
|
|
<div className="mx-4 mt-4 rounded-md bg-red-50 p-4 text-sm text-red-700 sm:mx-6 dark:bg-red-900/30 dark:text-red-300">
|
|
{combinedError}
|
|
</div>
|
|
)}
|
|
|
|
<div className="min-h-0 flex-1 overflow-y-auto px-4 py-6 sm:px-6">
|
|
{activeStep === 'masterData' && (
|
|
<div className="space-y-6">
|
|
<StepHeader
|
|
title="Stammdaten"
|
|
description="Die Einsatznummer ist erforderlich. Alles Weitere kannst du direkt oder später ergänzen."
|
|
/>
|
|
|
|
<div className="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-6">
|
|
<div className="sm:col-span-3">
|
|
<label htmlFor="createOperationNumber" className="block text-sm/6 font-medium text-gray-900 dark:text-white">
|
|
Einsatznummer *
|
|
</label>
|
|
<div className="mt-2">
|
|
<input
|
|
id="createOperationNumber"
|
|
type="text"
|
|
inputMode="numeric"
|
|
maxLength={11}
|
|
placeholder="000-00-0000"
|
|
value={formValues.operationNumber}
|
|
onChange={(event) => updateField('operationNumber', event.target.value)}
|
|
className={inputClassName}
|
|
autoFocus
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="sm:col-span-3">
|
|
<label htmlFor="createOperationName" className="block text-sm/6 font-medium text-gray-900 dark:text-white">
|
|
Einsatzname
|
|
</label>
|
|
<div className="mt-2">
|
|
<input
|
|
id="createOperationName"
|
|
type="text"
|
|
value={formValues.operationName}
|
|
onChange={(event) => updateField('operationName', event.target.value)}
|
|
className={inputClassName}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="sm:col-span-3">
|
|
<label htmlFor="createOffense" className="block text-sm/6 font-medium text-gray-900 dark:text-white">
|
|
Delikt
|
|
</label>
|
|
<div className="mt-2">
|
|
<input
|
|
id="createOffense"
|
|
type="text"
|
|
value={formValues.offense}
|
|
onChange={(event) => updateField('offense', event.target.value)}
|
|
className={inputClassName}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{activeStep === 'addresses' && (
|
|
<div className="space-y-6">
|
|
<StepHeader
|
|
title="Adressen"
|
|
description="KW und Zielobjekt werden mit AddressCombobox und Karten-Vorschau erfasst."
|
|
/>
|
|
|
|
<div className="grid grid-cols-1 gap-4 xl:grid-cols-2">
|
|
<AddressCombobox
|
|
id="createKwAddress"
|
|
name="createKwAddressDisplay"
|
|
label="KW"
|
|
value={formValues.kwAddress}
|
|
onChange={(value) => updateField('kwAddress', value)}
|
|
placeholder="KW-Adresse suchen oder eingeben"
|
|
/>
|
|
|
|
<AddressCombobox
|
|
id="createTargetObject"
|
|
name="createTargetObjectDisplay"
|
|
label="Zielobjekt"
|
|
value={formValues.targetObject}
|
|
onChange={(value) => updateField('targetObject', value)}
|
|
placeholder="Adresse suchen oder Zielobjekt eingeben"
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{activeStep === 'responsibilities' && (
|
|
<div className="space-y-6">
|
|
<StepHeader
|
|
title="Zuständigkeiten"
|
|
description="Sachbearbeitung, Einsatzleitung und eingesetztes Team."
|
|
/>
|
|
|
|
<div className="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-6">
|
|
<div className="sm:col-span-3 xl:col-span-2">
|
|
<label htmlFor="createCaseWorker" className="block text-sm/6 font-medium text-gray-900 dark:text-white">
|
|
Sachbearbeitung
|
|
</label>
|
|
<div className="mt-2">
|
|
<input
|
|
id="createCaseWorker"
|
|
type="text"
|
|
value={formValues.caseWorker}
|
|
onChange={(event) => updateField('caseWorker', event.target.value)}
|
|
className={inputClassName}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="sm:col-span-3 xl:col-span-2">
|
|
<label htmlFor="createOperationLeader" className="block text-sm/6 font-medium text-gray-900 dark:text-white">
|
|
Einsatzleiter
|
|
</label>
|
|
<div className="mt-2">
|
|
<input
|
|
id="createOperationLeader"
|
|
type="text"
|
|
value={formValues.operationLeader}
|
|
onChange={(event) => updateField('operationLeader', event.target.value)}
|
|
className={inputClassName}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="sm:col-span-3 xl:col-span-2">
|
|
<label htmlFor="createOperationTeam" className="block text-sm/6 font-medium text-gray-900 dark:text-white">
|
|
Einsatzteam
|
|
</label>
|
|
<div className="mt-2">
|
|
<input
|
|
id="createOperationTeam"
|
|
type="text"
|
|
value={formValues.operationTeam}
|
|
onChange={(event) => updateField('operationTeam', event.target.value)}
|
|
className={inputClassName}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{activeStep === 'notes' && (
|
|
<div className="space-y-6">
|
|
<StepHeader
|
|
title="Zusatzinformationen"
|
|
description="Legende und Bemerkung können beim Anlegen oder später ergänzt werden."
|
|
/>
|
|
|
|
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
|
<div>
|
|
<label htmlFor="createLegend" className="block text-sm/6 font-medium text-gray-900 dark:text-white">
|
|
Legende
|
|
</label>
|
|
<div className="mt-2">
|
|
<textarea
|
|
id="createLegend"
|
|
rows={6}
|
|
value={formValues.legend}
|
|
onChange={(event) => updateField('legend', event.target.value)}
|
|
className={textareaClassName}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="createRemark" className="block text-sm/6 font-medium text-gray-900 dark:text-white">
|
|
Bemerkung
|
|
</label>
|
|
<div className="mt-2">
|
|
<textarea
|
|
id="createRemark"
|
|
rows={6}
|
|
value={formValues.remark}
|
|
onChange={(event) => updateField('remark', event.target.value)}
|
|
className={textareaClassName}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{activeStep === 'equipment' && (
|
|
<div className="space-y-6">
|
|
<StepHeader
|
|
title="Technik"
|
|
description="Erfasse vorgesehene oder eingesetzte technische Ausstattung."
|
|
/>
|
|
|
|
<div className="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-2 xl:grid-cols-3">
|
|
<div>
|
|
<label htmlFor="createCamera" className="block text-sm/6 font-medium text-gray-900 dark:text-white">
|
|
Kamera
|
|
</label>
|
|
<div className="mt-2">
|
|
<input
|
|
id="createCamera"
|
|
type="text"
|
|
value={formValues.camera}
|
|
onChange={(event) => updateField('camera', event.target.value)}
|
|
className={inputClassName}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="createRouter" className="block text-sm/6 font-medium text-gray-900 dark:text-white">
|
|
Router
|
|
</label>
|
|
<div className="mt-2">
|
|
<input
|
|
id="createRouter"
|
|
type="text"
|
|
value={formValues.router}
|
|
onChange={(event) => updateField('router', event.target.value)}
|
|
className={inputClassName}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="createSwitchbox" className="block text-sm/6 font-medium text-gray-900 dark:text-white">
|
|
Switchbox
|
|
</label>
|
|
<div className="mt-2">
|
|
<input
|
|
id="createSwitchbox"
|
|
type="text"
|
|
value={formValues.switchbox}
|
|
onChange={(event) => updateField('switchbox', event.target.value)}
|
|
className={inputClassName}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="createLaptop" className="block text-sm/6 font-medium text-gray-900 dark:text-white">
|
|
Laptop
|
|
</label>
|
|
<div className="mt-2">
|
|
<input
|
|
id="createLaptop"
|
|
type="text"
|
|
value={formValues.laptop}
|
|
onChange={(event) => updateField('laptop', event.target.value)}
|
|
className={inputClassName}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{activeStep === 'review' && (
|
|
<div className="space-y-6">
|
|
<StepHeader
|
|
title="Einsatz prüfen"
|
|
description="Kontrolliere die Angaben. Danach kannst du den Einsatz speichern."
|
|
/>
|
|
|
|
<dl className="grid grid-cols-1 gap-4 rounded-lg border border-gray-200 bg-gray-50 p-4 sm:grid-cols-2 lg:grid-cols-3 dark:border-white/10 dark:bg-white/5">
|
|
<ReviewItem label="Einsatznummer" value={formValues.operationNumber} />
|
|
<ReviewItem label="Einsatzname" value={formValues.operationName} />
|
|
<ReviewItem label="Delikt" value={formValues.offense} />
|
|
<ReviewItem label="Sachbearbeitung" value={formValues.caseWorker} />
|
|
<ReviewItem label="KW" value={formValues.kwAddress} />
|
|
<ReviewItem label="Zielobjekt" value={formValues.targetObject} />
|
|
<ReviewItem label="Einsatzleiter" value={formValues.operationLeader} />
|
|
<ReviewItem label="Einsatzteam" value={formValues.operationTeam} />
|
|
<ReviewItem label="Kamera" value={formValues.camera} />
|
|
<ReviewItem label="Router" value={formValues.router} />
|
|
<ReviewItem label="Switchbox" value={formValues.switchbox} />
|
|
<ReviewItem label="Laptop" value={formValues.laptop} />
|
|
<ReviewItem label="Legende" value={formValues.legend} />
|
|
<ReviewItem label="Bemerkung" value={formValues.remark} />
|
|
</dl>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-3 border-t border-gray-200 px-4 py-4 sm:flex-row sm:items-center sm:justify-between sm:px-6 dark:border-white/10">
|
|
<div className="text-sm text-gray-500 dark:text-gray-400">
|
|
Schritt {activeStepIndex + 1} von {setupSteps.length}
|
|
</div>
|
|
|
|
<div className="flex justify-end gap-x-3">
|
|
<Button
|
|
type="button"
|
|
variant="secondary"
|
|
color="gray"
|
|
onClick={() => handleOpenChange(false)}
|
|
disabled={isSaving}
|
|
leadingIcon={<XMarkIcon aria-hidden="true" className="size-4" />}
|
|
>
|
|
Abbrechen
|
|
</Button>
|
|
|
|
{!isFirstStep && (
|
|
<Button
|
|
type="button"
|
|
variant="secondary"
|
|
color="gray"
|
|
onClick={goToPreviousStep}
|
|
disabled={isSaving}
|
|
leadingIcon={<ChevronLeftIcon aria-hidden="true" className="size-4" />}
|
|
>
|
|
Zurück
|
|
</Button>
|
|
)}
|
|
|
|
{!isLastStep && setupSteps[activeStepIndex].optional && (
|
|
<Button
|
|
type="button"
|
|
variant="secondary"
|
|
color="gray"
|
|
onClick={skipStep}
|
|
disabled={isSaving}
|
|
>
|
|
Überspringen
|
|
</Button>
|
|
)}
|
|
|
|
{!isLastStep ? (
|
|
<Button
|
|
type="button"
|
|
color="indigo"
|
|
onClick={goToNextStep}
|
|
disabled={isSaving}
|
|
leadingIcon={<ChevronRightIcon aria-hidden="true" className="size-4" />}
|
|
>
|
|
Weiter
|
|
</Button>
|
|
) : (
|
|
<Button
|
|
type="submit"
|
|
color="indigo"
|
|
isLoading={isSaving}
|
|
leadingIcon={<CheckIcon aria-hidden="true" className="size-4" />}
|
|
>
|
|
Einsatz speichern
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</Modal>
|
|
)
|
|
} |