configurable slot duration: UI picker + genRanges stepMinutes
- Fix TimeOfDayExtension.add() to handle minute overflow via total-minutes math - genRanges() now takes stepMinutes param (default 30, clamp 5-480) - Profesional model: slotDurationMinutes field (parsed from slot_duration_minutes) - ProfessionalFormProvider: saves slot duration alongside schedules on Guardar - schedule_view: _SlotDurationPicker chip selector (15/20/30/45/60/90/120 min) - ProfessionalCalendarView + CalendarView: pass slotDurationMinutes to genRanges Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
46acad0b55
commit
920ae7144d
@@ -11,6 +11,63 @@ import 'package:flutter/material.dart';
|
||||
import 'package:prosapp_web_app/ui/shared/widgets/schedule_day_tile.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
// ── Slot duration options ──────────────────────────────────────────────────
|
||||
const _kSlotOptions = [
|
||||
(15, '15 min'),
|
||||
(20, '20 min'),
|
||||
(30, '30 min'),
|
||||
(45, '45 min'),
|
||||
(60, '1 hora'),
|
||||
(90, '1 h 30'),
|
||||
(120, '2 horas'),
|
||||
];
|
||||
|
||||
class _SlotDurationPicker extends StatelessWidget {
|
||||
final int value;
|
||||
final ValueChanged<int> onChanged;
|
||||
const _SlotDurationPicker({required this.value, required this.onChanged});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Duración de cada cita',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: _kSlotOptions.map((opt) {
|
||||
final (mins, label) = opt;
|
||||
final selected = value == mins;
|
||||
return ChoiceChip(
|
||||
label: Text(label),
|
||||
selected: selected,
|
||||
onSelected: (_) => onChanged(mins),
|
||||
selectedColor: Colors.blue.shade400,
|
||||
labelStyle: TextStyle(
|
||||
color: selected ? Colors.white : null,
|
||||
fontWeight: selected ? FontWeight.w700 : FontWeight.normal,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Los usuarios verán bloques de $value min en el calendario.',
|
||||
style: TextStyle(fontSize: 11, color: Colors.grey.shade600),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ScheduleView extends StatefulWidget {
|
||||
const ScheduleView({super.key});
|
||||
|
||||
@@ -540,9 +597,13 @@ class _ScheduleViewState extends State<ScheduleView> {
|
||||
);
|
||||
},
|
||||
),
|
||||
const Divider(
|
||||
height: 20,
|
||||
const Divider(height: 20),
|
||||
_SlotDurationPicker(
|
||||
value: profesional.slotDurationMinutes,
|
||||
onChanged: (v) => professionalFormProvider
|
||||
.copyProfesionalWith(slotDurationMinutes: v),
|
||||
),
|
||||
const Divider(height: 20),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 10),
|
||||
child: ConstrainedBox(
|
||||
|
||||
Reference in New Issue
Block a user