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) {
|
||||
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 [customDurationValue, setCustomDurationValue] = React.useState("");
|
||||
const fraction = heatMapFraction || 60;
|
||||
const durationOptions = [fraction, fraction * 2, fraction * 3, fraction * 4];
|
||||
const customDurationMinutes = Number(customDurationValue);
|
||||
const isValidCustomDuration = Number.isInteger(customDurationMinutes) && customDurationMinutes > 0;
|
||||
const isValidCustomDuration =
|
||||
Number.isInteger(customDurationMinutes) && customDurationMinutes > 0;
|
||||
|
||||
const selectDurationOption = (duration: number) => {
|
||||
setIsCustomDuration(false);
|
||||
@@ -65,390 +99,518 @@ export default function OnboardingServiceFlow(props: OnboardingServiceFlowProps)
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<QuestionCard
|
||||
isActive={currentStepIndex === 0}
|
||||
title="Configura tus Servicios"
|
||||
description="Ya creaste tu negocio. Ahora necesitas configurar al menos un servicio para que tus clientes puedan empezar a reservar."
|
||||
onNext={handleNext}
|
||||
nextLabel="Comenzar"
|
||||
<>
|
||||
<QuestionCard
|
||||
isActive={currentStepIndex === 0}
|
||||
title="Configura tus Servicios"
|
||||
description="Ya creaste tu negocio. Ahora necesitas configurar al menos un servicio para que tus clientes puedan empezar a reservar."
|
||||
onNext={handleNext}
|
||||
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!
|
||||
</div>
|
||||
</QuestionCard>
|
||||
Vamos a crear el primer servicio de tu negocio. ¡Es muy fácil!
|
||||
</div>
|
||||
</QuestionCard>
|
||||
|
||||
<QuestionCard
|
||||
isActive={currentStepIndex === 1}
|
||||
title="1. Nombre y Descripción"
|
||||
description="Elige un nombre atractivo y describe de qué trata el servicio."
|
||||
onNext={handleNext}
|
||||
disableNext={serviceName.trim().length < 3 || serviceDescription.trim().length < 5}
|
||||
<QuestionCard
|
||||
isActive={currentStepIndex === 1}
|
||||
title="1. Nombre y Descripción"
|
||||
description="Elige un nombre atractivo y describe de qué trata el servicio."
|
||||
onNext={handleNext}
|
||||
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
|
||||
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)}
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
placeholder="Duración en minutos"
|
||||
value={customDurationValue}
|
||||
onChange={(e) => updateCustomDuration(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (serviceName.trim().length >= 3 && serviceDescription.trim().length >= 5) {
|
||||
if (isValidCustomDuration) {
|
||||
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
|
||||
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'
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
fontSize: "13px",
|
||||
marginTop: "8px",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
Otro
|
||||
Ingresá la duración total del servicio expresada en minutos.
|
||||
</div>
|
||||
</div>
|
||||
{isCustomDuration && (
|
||||
<div style={{ width: '100%', marginTop: '16px' }}>
|
||||
<QuestionInput
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
placeholder="Duración en minutos"
|
||||
value={customDurationValue}
|
||||
onChange={(e) => updateCustomDuration(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (isValidCustomDuration) {
|
||||
handleKeyDown(e, handleNext);
|
||||
}
|
||||
)}
|
||||
</QuestionCard>
|
||||
|
||||
<QuestionCard
|
||||
isActive={currentStepIndex === 3}
|
||||
title="3. Precio y Cupos"
|
||||
description="¿Cuánto cuesta y a cuántas personas puede atender cada profesional a la vez en este servicio?"
|
||||
onNext={handleCreateService}
|
||||
disableNext={isCreating}
|
||||
nextLabel={isCreating ? "Guardando..." : "Crear Servicio"}
|
||||
>
|
||||
<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' }}>
|
||||
Ingresá la duración total del servicio expresada en minutos.
|
||||
</div>
|
||||
</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>
|
||||
)}
|
||||
</QuestionCard>
|
||||
</div>
|
||||
</QuestionCard>
|
||||
|
||||
<QuestionCard
|
||||
isActive={currentStepIndex === 3}
|
||||
title="3. Precio y Cupos"
|
||||
description="¿Cuánto cuesta y a cuántas personas puedes atender a la vez en este servicio?"
|
||||
onNext={handleCreateService}
|
||||
disableNext={isCreating}
|
||||
nextLabel={isCreating ? "Guardando..." : "Crear Servicio"}
|
||||
<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={{ 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'
|
||||
}}
|
||||
/>
|
||||
</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={{
|
||||
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)",
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '14px', width: '100%' }}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAssignCreatedServiceToMe}
|
||||
disabled={isCreating}
|
||||
{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%",
|
||||
}}
|
||||
>
|
||||
<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={{
|
||||
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
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "10px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{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={{ display: 'flex', flexDirection: 'column', gap: '10px', width: '100%' }}>
|
||||
{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 && (
|
||||
<>
|
||||
>
|
||||
{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={handleFinishWithoutAssignment}
|
||||
disabled={isCreating}
|
||||
onClick={handleAssignCreatedServiceToCollaborators}
|
||||
disabled={
|
||||
isCreating ||
|
||||
selectedServiceCollaboratorIds.length === 0
|
||||
}
|
||||
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
|
||||
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,
|
||||
}}
|
||||
>
|
||||
Lo configuro después
|
||||
{isCreating ? "Asignando..." : "Asignar servicio"}
|
||||
</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',
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!serviceAssignmentWarning && (
|
||||
<>
|
||||
<button
|
||||
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,
|
||||
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>
|
||||
</>
|
||||
)}
|
||||
{serviceAssignmentError && (
|
||||
<div style={{
|
||||
padding: '12px 14px',
|
||||
borderRadius: '12px',
|
||||
background: 'rgba(255, 99, 132, 0.12)',
|
||||
border: '1px solid rgba(255, 99, 132, 0.35)',
|
||||
color: '#fecdd3',
|
||||
fontSize: '14px',
|
||||
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>
|
||||
</>
|
||||
)}
|
||||
{serviceAssignmentError && (
|
||||
<div
|
||||
style={{
|
||||
padding: "12px 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,
|
||||
textAlign: 'center'
|
||||
}}>
|
||||
{serviceAssignmentError}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</QuestionCard>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{serviceAssignmentError}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</QuestionCard>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user