feat: mejorar la legibilidad y estructura del componente OnboardingServiceFlow
This commit is contained in:
+518
-356
@@ -39,13 +39,47 @@ type OnboardingServiceFlowProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function OnboardingServiceFlow(props: OnboardingServiceFlowProps) {
|
export default function OnboardingServiceFlow(props: OnboardingServiceFlowProps) {
|
||||||
const { heatMapFraction, currentStepIndex, handleNext, serviceName, setServiceName, serviceDescription, setServiceDescription, handleKeyDown, serviceLength, setServiceLength, handleCreateService, isCreating, servicePrice, setServicePrice, serviceLimit, setServiceLimit, serviceCreateError, handleUploadServiceImage, serviceImage, setServiceImage, isStandaloneNewService, handleAssignCreatedServiceToMe, handleShowCollaboratorAssignment, handleToggleServiceCollaborator, handleAssignCreatedServiceToCollaborators, handleFinishWithoutAssignment, serviceAssignmentError, serviceAssignmentWarning, serviceAssignmentMode, serviceAssignmentCollaborators, selectedServiceCollaboratorIds, isLoadingServiceCollaborators } = props;
|
const {
|
||||||
|
heatMapFraction,
|
||||||
|
currentStepIndex,
|
||||||
|
handleNext,
|
||||||
|
serviceName,
|
||||||
|
setServiceName,
|
||||||
|
serviceDescription,
|
||||||
|
setServiceDescription,
|
||||||
|
handleKeyDown,
|
||||||
|
serviceLength,
|
||||||
|
setServiceLength,
|
||||||
|
handleCreateService,
|
||||||
|
isCreating,
|
||||||
|
servicePrice,
|
||||||
|
setServicePrice,
|
||||||
|
serviceLimit,
|
||||||
|
setServiceLimit,
|
||||||
|
serviceCreateError,
|
||||||
|
handleUploadServiceImage,
|
||||||
|
serviceImage,
|
||||||
|
setServiceImage,
|
||||||
|
isStandaloneNewService,
|
||||||
|
handleAssignCreatedServiceToMe,
|
||||||
|
handleShowCollaboratorAssignment,
|
||||||
|
handleToggleServiceCollaborator,
|
||||||
|
handleAssignCreatedServiceToCollaborators,
|
||||||
|
handleFinishWithoutAssignment,
|
||||||
|
serviceAssignmentError,
|
||||||
|
serviceAssignmentWarning,
|
||||||
|
serviceAssignmentMode,
|
||||||
|
serviceAssignmentCollaborators,
|
||||||
|
selectedServiceCollaboratorIds,
|
||||||
|
isLoadingServiceCollaborators,
|
||||||
|
} = props;
|
||||||
const [isCustomDuration, setIsCustomDuration] = React.useState(false);
|
const [isCustomDuration, setIsCustomDuration] = React.useState(false);
|
||||||
const [customDurationValue, setCustomDurationValue] = React.useState("");
|
const [customDurationValue, setCustomDurationValue] = React.useState("");
|
||||||
const fraction = heatMapFraction || 60;
|
const fraction = heatMapFraction || 60;
|
||||||
const durationOptions = [fraction, fraction * 2, fraction * 3, fraction * 4];
|
const durationOptions = [fraction, fraction * 2, fraction * 3, fraction * 4];
|
||||||
const customDurationMinutes = Number(customDurationValue);
|
const customDurationMinutes = Number(customDurationValue);
|
||||||
const isValidCustomDuration = Number.isInteger(customDurationMinutes) && customDurationMinutes > 0;
|
const isValidCustomDuration =
|
||||||
|
Number.isInteger(customDurationMinutes) && customDurationMinutes > 0;
|
||||||
|
|
||||||
const selectDurationOption = (duration: number) => {
|
const selectDurationOption = (duration: number) => {
|
||||||
setIsCustomDuration(false);
|
setIsCustomDuration(false);
|
||||||
@@ -65,390 +99,518 @@ export default function OnboardingServiceFlow(props: OnboardingServiceFlowProps)
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<QuestionCard
|
<QuestionCard
|
||||||
isActive={currentStepIndex === 0}
|
isActive={currentStepIndex === 0}
|
||||||
title="Configura tus Servicios"
|
title="Configura tus Servicios"
|
||||||
description="Ya creaste tu negocio. Ahora necesitas configurar al menos un servicio para que tus clientes puedan empezar a reservar."
|
description="Ya creaste tu negocio. Ahora necesitas configurar al menos un servicio para que tus clientes puedan empezar a reservar."
|
||||||
onNext={handleNext}
|
onNext={handleNext}
|
||||||
nextLabel="Comenzar"
|
nextLabel="Comenzar"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
color: "rgba(255, 255, 255, 0.7)",
|
||||||
|
textAlign: "center",
|
||||||
|
padding: "10px",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ color: "rgba(255, 255, 255, 0.7)", textAlign: "center", padding: "10px" }}>
|
Vamos a crear el primer servicio de tu negocio. ¡Es muy fácil!
|
||||||
Vamos a crear el primer servicio de tu negocio. ¡Es muy fácil!
|
</div>
|
||||||
</div>
|
</QuestionCard>
|
||||||
</QuestionCard>
|
|
||||||
|
|
||||||
<QuestionCard
|
<QuestionCard
|
||||||
isActive={currentStepIndex === 1}
|
isActive={currentStepIndex === 1}
|
||||||
title="1. Nombre y Descripción"
|
title="1. Nombre y Descripción"
|
||||||
description="Elige un nombre atractivo y describe de qué trata el servicio."
|
description="Elige un nombre atractivo y describe de qué trata el servicio."
|
||||||
onNext={handleNext}
|
onNext={handleNext}
|
||||||
disableNext={serviceName.trim().length < 3 || serviceDescription.trim().length < 5}
|
disableNext={serviceName.trim().length < 3 || serviceDescription.trim().length < 5}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{ display: "flex", flexDirection: "column", gap: "15px", width: "100%" }}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px', width: '100%' }}>
|
<QuestionInput
|
||||||
|
placeholder="Ej. Corte clásico con lavado"
|
||||||
|
value={serviceName}
|
||||||
|
onChange={(e) => setServiceName(e.target.value)}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<QuestionInput
|
||||||
|
placeholder="Breve descripción del servicio..."
|
||||||
|
value={serviceDescription}
|
||||||
|
onChange={(e) => setServiceDescription(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (
|
||||||
|
serviceName.trim().length >= 3 &&
|
||||||
|
serviceDescription.trim().length >= 5
|
||||||
|
) {
|
||||||
|
handleKeyDown(e, handleNext);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</QuestionCard>
|
||||||
|
|
||||||
|
<QuestionCard
|
||||||
|
isActive={currentStepIndex === 2}
|
||||||
|
title="2. Duración del Servicio"
|
||||||
|
description={`Elige cuánto tiempo tomará. Las opciones están basadas en tu fracción de agenda (${fraction} min).`}
|
||||||
|
onNext={handleNext}
|
||||||
|
disableNext={!serviceLength || (isCustomDuration && !isValidCustomDuration)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "10px",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
width: "100%",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{durationOptions.map((val) => {
|
||||||
|
const hours = Math.floor(val / 60);
|
||||||
|
const minutes = val % 60;
|
||||||
|
const label =
|
||||||
|
hours > 0
|
||||||
|
? `${hours}h ${minutes > 0 ? minutes + "m" : ""}`.trim()
|
||||||
|
: `${minutes} min`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={val}
|
||||||
|
onClick={() => selectDurationOption(val)}
|
||||||
|
style={{
|
||||||
|
padding: "12px 24px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
cursor: "pointer",
|
||||||
|
background:
|
||||||
|
!isCustomDuration && serviceLength === val
|
||||||
|
? "var(--wine-red)"
|
||||||
|
: "rgba(255,255,255,0.05)",
|
||||||
|
border: `2px solid ${!isCustomDuration && serviceLength === val ? "var(--wine-red)" : "rgba(255,255,255,0.1)"}`,
|
||||||
|
color: "white",
|
||||||
|
fontWeight:
|
||||||
|
!isCustomDuration && serviceLength === val
|
||||||
|
? "bold"
|
||||||
|
: "normal",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
fontSize: "16px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<div
|
||||||
|
onClick={selectCustomDuration}
|
||||||
|
style={{
|
||||||
|
padding: "12px 24px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
cursor: "pointer",
|
||||||
|
background: isCustomDuration
|
||||||
|
? "var(--wine-red)"
|
||||||
|
: "rgba(255,255,255,0.05)",
|
||||||
|
border: `2px solid ${isCustomDuration ? "var(--wine-red)" : "rgba(255,255,255,0.1)"}`,
|
||||||
|
color: "white",
|
||||||
|
fontWeight: isCustomDuration ? "bold" : "normal",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
fontSize: "16px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Otro
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isCustomDuration && (
|
||||||
|
<div style={{ width: "100%", marginTop: "16px" }}>
|
||||||
<QuestionInput
|
<QuestionInput
|
||||||
placeholder="Ej. Corte clásico con lavado"
|
type="number"
|
||||||
value={serviceName}
|
min="1"
|
||||||
onChange={(e) => setServiceName(e.target.value)}
|
step="1"
|
||||||
autoFocus
|
placeholder="Duración en minutos"
|
||||||
/>
|
value={customDurationValue}
|
||||||
<QuestionInput
|
onChange={(e) => updateCustomDuration(e.target.value)}
|
||||||
placeholder="Breve descripción del servicio..."
|
|
||||||
value={serviceDescription}
|
|
||||||
onChange={(e) => setServiceDescription(e.target.value)}
|
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (serviceName.trim().length >= 3 && serviceDescription.trim().length >= 5) {
|
if (isValidCustomDuration) {
|
||||||
handleKeyDown(e, handleNext);
|
handleKeyDown(e, handleNext);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</QuestionCard>
|
|
||||||
|
|
||||||
<QuestionCard
|
|
||||||
isActive={currentStepIndex === 2}
|
|
||||||
title="2. Duración del Servicio"
|
|
||||||
description={`Elige cuánto tiempo tomará. Las opciones están basadas en tu fracción de agenda (${fraction} min).`}
|
|
||||||
onNext={handleNext}
|
|
||||||
disableNext={!serviceLength || (isCustomDuration && !isValidCustomDuration)}
|
|
||||||
>
|
|
||||||
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap', width: '100%', justifyContent: 'center' }}>
|
|
||||||
{durationOptions.map(val => {
|
|
||||||
const hours = Math.floor(val / 60);
|
|
||||||
const minutes = val % 60;
|
|
||||||
const label = hours > 0
|
|
||||||
? `${hours}h ${minutes > 0 ? minutes + 'm' : ''}`.trim()
|
|
||||||
: `${minutes} min`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={val}
|
|
||||||
onClick={() => selectDurationOption(val)}
|
|
||||||
style={{
|
|
||||||
padding: '12px 24px',
|
|
||||||
borderRadius: '12px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
background: !isCustomDuration && serviceLength === val ? 'var(--wine-red)' : 'rgba(255,255,255,0.05)',
|
|
||||||
border: `2px solid ${!isCustomDuration && serviceLength === val ? 'var(--wine-red)' : 'rgba(255,255,255,0.1)'}`,
|
|
||||||
color: 'white',
|
|
||||||
fontWeight: !isCustomDuration && serviceLength === val ? 'bold' : 'normal',
|
|
||||||
transition: 'all 0.2s ease',
|
|
||||||
fontSize: '16px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<div
|
<div
|
||||||
onClick={selectCustomDuration}
|
|
||||||
style={{
|
style={{
|
||||||
padding: '12px 24px',
|
color: "rgba(255,255,255,0.6)",
|
||||||
borderRadius: '12px',
|
fontSize: "13px",
|
||||||
cursor: 'pointer',
|
marginTop: "8px",
|
||||||
background: isCustomDuration ? 'var(--wine-red)' : 'rgba(255,255,255,0.05)',
|
textAlign: "center",
|
||||||
border: `2px solid ${isCustomDuration ? 'var(--wine-red)' : 'rgba(255,255,255,0.1)'}`,
|
|
||||||
color: 'white',
|
|
||||||
fontWeight: isCustomDuration ? 'bold' : 'normal',
|
|
||||||
transition: 'all 0.2s ease',
|
|
||||||
fontSize: '16px'
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Otro
|
Ingresá la duración total del servicio expresada en minutos.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isCustomDuration && (
|
)}
|
||||||
<div style={{ width: '100%', marginTop: '16px' }}>
|
</QuestionCard>
|
||||||
<QuestionInput
|
|
||||||
type="number"
|
<QuestionCard
|
||||||
min="1"
|
isActive={currentStepIndex === 3}
|
||||||
step="1"
|
title="3. Precio y Cupos"
|
||||||
placeholder="Duración en minutos"
|
description="¿Cuánto cuesta y a cuántas personas puede atender cada profesional a la vez en este servicio?"
|
||||||
value={customDurationValue}
|
onNext={handleCreateService}
|
||||||
onChange={(e) => updateCustomDuration(e.target.value)}
|
disableNext={isCreating}
|
||||||
onKeyDown={(e) => {
|
nextLabel={isCreating ? "Guardando..." : "Crear Servicio"}
|
||||||
if (isValidCustomDuration) {
|
>
|
||||||
handleKeyDown(e, handleNext);
|
<div
|
||||||
}
|
style={{ display: "flex", flexDirection: "column", gap: "20px", width: "100%" }}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div style={{ color: "white", marginBottom: "10px", fontSize: "14px" }}>
|
||||||
|
Precio Original
|
||||||
|
</div>
|
||||||
|
<div style={{ position: "relative" }}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
left: "15px",
|
||||||
|
top: "50%",
|
||||||
|
transform: "translateY(-50%)",
|
||||||
|
color: "white",
|
||||||
|
fontSize: "18px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
$
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
inputMode="decimal"
|
||||||
|
placeholder="0.00"
|
||||||
|
value={servicePrice}
|
||||||
|
onChange={(e) => setServicePrice(e.target.value)}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
padding: "15px 15px 15px 35px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
border: "1px solid rgba(255,255,255,0.2)",
|
||||||
|
background: "rgba(255,255,255,0.05)",
|
||||||
|
color: "white",
|
||||||
|
fontSize: "18px",
|
||||||
|
outline: "none",
|
||||||
}}
|
}}
|
||||||
autoFocus
|
|
||||||
/>
|
/>
|
||||||
<div style={{ color: 'rgba(255,255,255,0.6)', fontSize: '13px', marginTop: '8px', textAlign: 'center' }}>
|
</div>
|
||||||
Ingresá la duración total del servicio expresada en minutos.
|
</div>
|
||||||
</div>
|
|
||||||
|
<div>
|
||||||
|
<div style={{ color: "white", marginBottom: "10px", fontSize: "14px" }}>
|
||||||
|
Cupos Simultáneos (Por defecto 1)
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", gap: "10px", flexWrap: "wrap" }}>
|
||||||
|
{[1, 2, 3, 5, 10].map((val) => (
|
||||||
|
<div
|
||||||
|
key={val}
|
||||||
|
onClick={() => setServiceLimit(val)}
|
||||||
|
style={{
|
||||||
|
padding: "8px 16px",
|
||||||
|
borderRadius: "8px",
|
||||||
|
cursor: "pointer",
|
||||||
|
background:
|
||||||
|
serviceLimit === val
|
||||||
|
? "var(--wine-red)"
|
||||||
|
: "rgba(255,255,255,0.1)",
|
||||||
|
border: `1px solid ${serviceLimit === val ? "var(--wine-red)" : "rgba(255,255,255,0.2)"}`,
|
||||||
|
color: "white",
|
||||||
|
fontWeight: serviceLimit === val ? "bold" : "normal",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{val}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{serviceCreateError && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "14px 16px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
background: "rgba(255, 99, 132, 0.12)",
|
||||||
|
border: "1px solid rgba(255, 99, 132, 0.35)",
|
||||||
|
color: "#fecdd3",
|
||||||
|
fontSize: "14px",
|
||||||
|
lineHeight: 1.5,
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{serviceCreateError}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</QuestionCard>
|
</div>
|
||||||
|
</QuestionCard>
|
||||||
|
|
||||||
<QuestionCard
|
<QuestionCard
|
||||||
isActive={currentStepIndex === 3}
|
isActive={currentStepIndex === 4}
|
||||||
title="3. Precio y Cupos"
|
title="4. Imagen del Servicio"
|
||||||
description="¿Cuánto cuesta y a cuántas personas puedes atender a la vez en este servicio?"
|
description="Sube una foto representativa para que los clientes sepan qué ofreces. (Opcional)"
|
||||||
onNext={handleCreateService}
|
onNext={handleUploadServiceImage}
|
||||||
disableNext={isCreating}
|
disableNext={isCreating}
|
||||||
nextLabel={isCreating ? "Guardando..." : "Crear Servicio"}
|
nextLabel={
|
||||||
|
isCreating
|
||||||
|
? "Subiendo..."
|
||||||
|
: serviceImage
|
||||||
|
? isStandaloneNewService
|
||||||
|
? "Subir y continuar"
|
||||||
|
: "Subir y Finalizar"
|
||||||
|
: "Omitir por ahora"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "20px",
|
||||||
|
width: "100%",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '20px', width: '100%' }}>
|
<div
|
||||||
<div>
|
style={{
|
||||||
<div style={{ color: 'white', marginBottom: '10px', fontSize: '14px' }}>Precio Original</div>
|
width: "100%",
|
||||||
<div style={{ position: 'relative' }}>
|
height: "150px",
|
||||||
<span style={{ position: 'absolute', left: '15px', top: '50%', transform: 'translateY(-50%)', color: 'white', fontSize: '18px' }}>$</span>
|
border: "2px dashed rgba(255,255,255,0.3)",
|
||||||
<input
|
borderRadius: "12px",
|
||||||
type="text"
|
display: "flex",
|
||||||
inputMode="decimal"
|
alignItems: "center",
|
||||||
placeholder="0.00"
|
justifyContent: "center",
|
||||||
value={servicePrice}
|
position: "relative",
|
||||||
onChange={(e) => setServicePrice(e.target.value)}
|
overflow: "hidden",
|
||||||
style={{
|
cursor: "pointer",
|
||||||
width: '100%',
|
background: serviceImage ? "transparent" : "rgba(255,255,255,0.05)",
|
||||||
padding: '15px 15px 15px 35px',
|
}}
|
||||||
borderRadius: '12px',
|
|
||||||
border: '1px solid rgba(255,255,255,0.2)',
|
|
||||||
background: 'rgba(255,255,255,0.05)',
|
|
||||||
color: 'white',
|
|
||||||
fontSize: '18px',
|
|
||||||
outline: 'none'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div style={{ color: 'white', marginBottom: '10px', fontSize: '14px' }}>Cupos Simultáneos (Por defecto 1)</div>
|
|
||||||
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
|
|
||||||
{[1, 2, 3, 5, 10].map(val => (
|
|
||||||
<div
|
|
||||||
key={val}
|
|
||||||
onClick={() => setServiceLimit(val)}
|
|
||||||
style={{
|
|
||||||
padding: '8px 16px',
|
|
||||||
borderRadius: '8px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
background: serviceLimit === val ? 'var(--wine-red)' : 'rgba(255,255,255,0.1)',
|
|
||||||
border: `1px solid ${serviceLimit === val ? 'var(--wine-red)' : 'rgba(255,255,255,0.2)'}`,
|
|
||||||
color: 'white',
|
|
||||||
fontWeight: serviceLimit === val ? 'bold' : 'normal',
|
|
||||||
transition: 'all 0.2s ease'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{val}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{serviceCreateError && (
|
|
||||||
<div style={{
|
|
||||||
padding: '14px 16px',
|
|
||||||
borderRadius: '12px',
|
|
||||||
background: 'rgba(255, 99, 132, 0.12)',
|
|
||||||
border: '1px solid rgba(255, 99, 132, 0.35)',
|
|
||||||
color: '#fecdd3',
|
|
||||||
fontSize: '14px',
|
|
||||||
lineHeight: 1.5,
|
|
||||||
textAlign: 'center'
|
|
||||||
}}>
|
|
||||||
{serviceCreateError}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</QuestionCard>
|
|
||||||
|
|
||||||
<QuestionCard
|
|
||||||
isActive={currentStepIndex === 4}
|
|
||||||
title="4. Imagen del Servicio"
|
|
||||||
description="Sube una foto representativa para que los clientes sepan qué ofreces. (Opcional)"
|
|
||||||
onNext={handleUploadServiceImage}
|
|
||||||
disableNext={isCreating}
|
|
||||||
nextLabel={isCreating ? "Subiendo..." : (serviceImage ? (isStandaloneNewService ? "Subir y continuar" : "Subir y Finalizar") : "Omitir por ahora")}
|
|
||||||
>
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '20px', width: '100%', alignItems: 'center' }}>
|
|
||||||
<div style={{
|
|
||||||
width: '100%',
|
|
||||||
height: '150px',
|
|
||||||
border: '2px dashed rgba(255,255,255,0.3)',
|
|
||||||
borderRadius: '12px',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
position: 'relative',
|
|
||||||
overflow: 'hidden',
|
|
||||||
cursor: 'pointer',
|
|
||||||
background: serviceImage ? 'transparent' : 'rgba(255,255,255,0.05)'
|
|
||||||
}}>
|
|
||||||
{serviceImage ? (
|
|
||||||
<img
|
|
||||||
src={URL.createObjectURL(serviceImage)}
|
|
||||||
alt="Preview"
|
|
||||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<span style={{ color: 'rgba(255,255,255,0.6)' }}>Toca para seleccionar imagen</span>
|
|
||||||
)}
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
accept="image/*"
|
|
||||||
onChange={(e) => {
|
|
||||||
if (e.target.files && e.target.files.length > 0) {
|
|
||||||
setServiceImage(e.target.files[0]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
opacity: 0,
|
|
||||||
cursor: 'pointer'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{serviceCreateError && (
|
|
||||||
<div style={{
|
|
||||||
padding: '14px 16px',
|
|
||||||
borderRadius: '12px',
|
|
||||||
background: 'rgba(255, 99, 132, 0.12)',
|
|
||||||
border: '1px solid rgba(255, 99, 132, 0.35)',
|
|
||||||
color: '#fecdd3',
|
|
||||||
fontSize: '14px',
|
|
||||||
lineHeight: 1.5,
|
|
||||||
textAlign: 'center'
|
|
||||||
}}>
|
|
||||||
{serviceCreateError}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</QuestionCard>
|
|
||||||
|
|
||||||
{isStandaloneNewService && (
|
|
||||||
<QuestionCard
|
|
||||||
isActive={currentStepIndex === 5}
|
|
||||||
title="¿Quién brinda este servicio?"
|
|
||||||
description="Asigná este servicio a un colaborador para que pueda recibir reservas. Si todavía no querés hacerlo, podés configurarlo después."
|
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '14px', width: '100%' }}>
|
{serviceImage ? (
|
||||||
<button
|
<img
|
||||||
type="button"
|
src={URL.createObjectURL(serviceImage)}
|
||||||
onClick={handleAssignCreatedServiceToMe}
|
alt="Preview"
|
||||||
disabled={isCreating}
|
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span style={{ color: "rgba(255,255,255,0.6)" }}>
|
||||||
|
Toca para seleccionar imagen
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.files && e.target.files.length > 0) {
|
||||||
|
setServiceImage(e.target.files[0]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
opacity: 0,
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{serviceCreateError && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "14px 16px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
background: "rgba(255, 99, 132, 0.12)",
|
||||||
|
border: "1px solid rgba(255, 99, 132, 0.35)",
|
||||||
|
color: "#fecdd3",
|
||||||
|
fontSize: "14px",
|
||||||
|
lineHeight: 1.5,
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{serviceCreateError}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</QuestionCard>
|
||||||
|
|
||||||
|
{isStandaloneNewService && (
|
||||||
|
<QuestionCard
|
||||||
|
isActive={currentStepIndex === 5}
|
||||||
|
title="¿Quién brinda este servicio?"
|
||||||
|
description="Asigná este servicio a un colaborador para que pueda recibir reservas. Si todavía no querés hacerlo, podés configurarlo después."
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "14px",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleAssignCreatedServiceToMe}
|
||||||
|
disabled={isCreating}
|
||||||
|
style={{
|
||||||
|
padding: "14px 18px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
border: "1px solid var(--wine-red)",
|
||||||
|
background: "var(--wine-red)",
|
||||||
|
color: "white",
|
||||||
|
fontSize: "16px",
|
||||||
|
fontWeight: "bold",
|
||||||
|
cursor: isCreating ? "not-allowed" : "pointer",
|
||||||
|
opacity: isCreating ? 0.7 : 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isCreating ? "Asignando..." : "Yo"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleShowCollaboratorAssignment}
|
||||||
|
disabled={isCreating || isLoadingServiceCollaborators}
|
||||||
|
style={{
|
||||||
|
padding: "14px 18px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
border:
|
||||||
|
serviceAssignmentMode === "collaborators"
|
||||||
|
? "1px solid var(--wine-red)"
|
||||||
|
: "1px solid rgba(255,255,255,0.2)",
|
||||||
|
background:
|
||||||
|
serviceAssignmentMode === "collaborators"
|
||||||
|
? "rgba(128, 0, 64, 0.35)"
|
||||||
|
: "rgba(255,255,255,0.05)",
|
||||||
|
color: "white",
|
||||||
|
fontSize: "16px",
|
||||||
|
cursor:
|
||||||
|
isCreating || isLoadingServiceCollaborators
|
||||||
|
? "not-allowed"
|
||||||
|
: "pointer",
|
||||||
|
opacity: isCreating || isLoadingServiceCollaborators ? 0.7 : 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isLoadingServiceCollaborators
|
||||||
|
? "Cargando colaboradores..."
|
||||||
|
: "Uno o más colaboradores"}
|
||||||
|
</button>
|
||||||
|
{serviceAssignmentMode === "collaborators" && (
|
||||||
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: '14px 18px',
|
display: "flex",
|
||||||
borderRadius: '12px',
|
flexDirection: "column",
|
||||||
border: '1px solid var(--wine-red)',
|
gap: "10px",
|
||||||
background: 'var(--wine-red)',
|
width: "100%",
|
||||||
color: 'white',
|
|
||||||
fontSize: '16px',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
cursor: isCreating ? 'not-allowed' : 'pointer',
|
|
||||||
opacity: isCreating ? 0.7 : 1
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isCreating ? "Asignando..." : "Yo"}
|
{serviceAssignmentCollaborators.length === 0 &&
|
||||||
</button>
|
!isLoadingServiceCollaborators && (
|
||||||
<button
|
<div
|
||||||
type="button"
|
style={{
|
||||||
onClick={handleShowCollaboratorAssignment}
|
color: "rgba(255,255,255,0.7)",
|
||||||
disabled={isCreating || isLoadingServiceCollaborators}
|
fontSize: "14px",
|
||||||
style={{
|
textAlign: "center",
|
||||||
padding: '14px 18px',
|
}}
|
||||||
borderRadius: '12px',
|
>
|
||||||
border: serviceAssignmentMode === "collaborators" ? '1px solid var(--wine-red)' : '1px solid rgba(255,255,255,0.2)',
|
No encontramos colaboradores activos para esta
|
||||||
background: serviceAssignmentMode === "collaborators" ? 'rgba(128, 0, 64, 0.35)' : 'rgba(255,255,255,0.05)',
|
organización.
|
||||||
color: 'white',
|
</div>
|
||||||
fontSize: '16px',
|
)}
|
||||||
cursor: isCreating || isLoadingServiceCollaborators ? 'not-allowed' : 'pointer',
|
<CollaboratorPicker
|
||||||
opacity: isCreating || isLoadingServiceCollaborators ? 0.7 : 1
|
employees={serviceAssignmentCollaborators}
|
||||||
}}
|
selectedIds={selectedServiceCollaboratorIds}
|
||||||
>
|
multiple
|
||||||
{isLoadingServiceCollaborators ? "Cargando colaboradores..." : "Uno o más colaboradores"}
|
disabled={isCreating}
|
||||||
</button>
|
onToggle={handleToggleServiceCollaborator}
|
||||||
{serviceAssignmentMode === "collaborators" && (
|
/>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px', width: '100%' }}>
|
{serviceAssignmentCollaborators.length > 0 && (
|
||||||
{serviceAssignmentCollaborators.length === 0 && !isLoadingServiceCollaborators && (
|
|
||||||
<div style={{ color: 'rgba(255,255,255,0.7)', fontSize: '14px', textAlign: 'center' }}>
|
|
||||||
No encontramos colaboradores activos para esta organización.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<CollaboratorPicker
|
|
||||||
employees={serviceAssignmentCollaborators}
|
|
||||||
selectedIds={selectedServiceCollaboratorIds}
|
|
||||||
multiple
|
|
||||||
disabled={isCreating}
|
|
||||||
onToggle={handleToggleServiceCollaborator}
|
|
||||||
/>
|
|
||||||
{serviceAssignmentCollaborators.length > 0 && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={handleAssignCreatedServiceToCollaborators}
|
|
||||||
disabled={isCreating || selectedServiceCollaboratorIds.length === 0}
|
|
||||||
style={{
|
|
||||||
padding: '14px 18px',
|
|
||||||
borderRadius: '12px',
|
|
||||||
border: '1px solid var(--wine-red)',
|
|
||||||
background: 'var(--wine-red)',
|
|
||||||
color: 'white',
|
|
||||||
fontSize: '16px',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
cursor: isCreating || selectedServiceCollaboratorIds.length === 0 ? 'not-allowed' : 'pointer',
|
|
||||||
opacity: isCreating || selectedServiceCollaboratorIds.length === 0 ? 0.7 : 1
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isCreating ? "Asignando..." : "Asignar servicio"}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!serviceAssignmentWarning && (
|
|
||||||
<>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleFinishWithoutAssignment}
|
onClick={handleAssignCreatedServiceToCollaborators}
|
||||||
disabled={isCreating}
|
disabled={
|
||||||
|
isCreating ||
|
||||||
|
selectedServiceCollaboratorIds.length === 0
|
||||||
|
}
|
||||||
style={{
|
style={{
|
||||||
padding: '14px 18px',
|
padding: "14px 18px",
|
||||||
borderRadius: '12px',
|
borderRadius: "12px",
|
||||||
border: '1px solid rgba(255,255,255,0.2)',
|
border: "1px solid var(--wine-red)",
|
||||||
background: 'rgba(255,255,255,0.05)',
|
background: "var(--wine-red)",
|
||||||
color: 'white',
|
color: "white",
|
||||||
fontSize: '16px',
|
fontSize: "16px",
|
||||||
cursor: isCreating ? 'not-allowed' : 'pointer',
|
fontWeight: "bold",
|
||||||
opacity: isCreating ? 0.7 : 1
|
cursor:
|
||||||
|
isCreating ||
|
||||||
|
selectedServiceCollaboratorIds.length === 0
|
||||||
|
? "not-allowed"
|
||||||
|
: "pointer",
|
||||||
|
opacity:
|
||||||
|
isCreating ||
|
||||||
|
selectedServiceCollaboratorIds.length === 0
|
||||||
|
? 0.7
|
||||||
|
: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Lo configuro después
|
{isCreating ? "Asignando..." : "Asignar servicio"}
|
||||||
</button>
|
</button>
|
||||||
<div style={{
|
)}
|
||||||
padding: '12px 14px',
|
</div>
|
||||||
borderRadius: '12px',
|
)}
|
||||||
background: 'rgba(245, 158, 11, 0.12)',
|
{!serviceAssignmentWarning && (
|
||||||
border: '1px solid rgba(245, 158, 11, 0.35)',
|
<>
|
||||||
color: '#fde68a',
|
<button
|
||||||
fontSize: '14px',
|
type="button"
|
||||||
|
onClick={handleFinishWithoutAssignment}
|
||||||
|
disabled={isCreating}
|
||||||
|
style={{
|
||||||
|
padding: "14px 18px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
border: "1px solid rgba(255,255,255,0.2)",
|
||||||
|
background: "rgba(255,255,255,0.05)",
|
||||||
|
color: "white",
|
||||||
|
fontSize: "16px",
|
||||||
|
cursor: isCreating ? "not-allowed" : "pointer",
|
||||||
|
opacity: isCreating ? 0.7 : 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Lo configuro después
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "12px 14px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
background: "rgba(245, 158, 11, 0.12)",
|
||||||
|
border: "1px solid rgba(245, 158, 11, 0.35)",
|
||||||
|
color: "#fde68a",
|
||||||
|
fontSize: "14px",
|
||||||
lineHeight: 1.5,
|
lineHeight: 1.5,
|
||||||
textAlign: 'center'
|
textAlign: "center",
|
||||||
}}>
|
}}
|
||||||
Si lo configurás después, el servicio no va a estar disponible para reservas hasta que lo asignes a un colaborador con horarios cargados.
|
>
|
||||||
</div>
|
Si lo configurás después, el servicio no va a estar disponible
|
||||||
</>
|
para reservas hasta que lo asignes a un colaborador con horarios
|
||||||
)}
|
cargados.
|
||||||
{serviceAssignmentError && (
|
</div>
|
||||||
<div style={{
|
</>
|
||||||
padding: '12px 14px',
|
)}
|
||||||
borderRadius: '12px',
|
{serviceAssignmentError && (
|
||||||
background: 'rgba(255, 99, 132, 0.12)',
|
<div
|
||||||
border: '1px solid rgba(255, 99, 132, 0.35)',
|
style={{
|
||||||
color: '#fecdd3',
|
padding: "12px 14px",
|
||||||
fontSize: '14px',
|
borderRadius: "12px",
|
||||||
|
background: "rgba(255, 99, 132, 0.12)",
|
||||||
|
border: "1px solid rgba(255, 99, 132, 0.35)",
|
||||||
|
color: "#fecdd3",
|
||||||
|
fontSize: "14px",
|
||||||
lineHeight: 1.5,
|
lineHeight: 1.5,
|
||||||
textAlign: 'center'
|
textAlign: "center",
|
||||||
}}>
|
}}
|
||||||
{serviceAssignmentError}
|
>
|
||||||
</div>
|
{serviceAssignmentError}
|
||||||
)}
|
</div>
|
||||||
</div>
|
)}
|
||||||
</QuestionCard>
|
</div>
|
||||||
)}
|
</QuestionCard>
|
||||||
</>
|
)}
|
||||||
);
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user