显示时间表

问题描述 投票:0回答:1

我有两页,其中一页是用户的个人资料,他在日历中添加了可供人们预订的时间,另一页我可以在其中查看个人资料并选择可用的时段和日期。唯一的问题是,一个显示来自 Firebase 的数据,并添加了时间段(用户在其中插入可用日期 https://imgur.com/a/j7KGVn0),另一个显示未获取数据https://imgur.com/a/5fLKEln

class BookingCalendar extends StatefulWidget {
  @override
  _BookingCalendarState createState() => _BookingCalendarState();
}

class _BookingCalendarState extends State<BookingCalendar> {
  CalendarFormat _calendarFormat = CalendarFormat.month;
  DateTime _focusedDay = DateTime.now();
  DateTime? _selectedDay;
  Map<DateTime, List<TimeSlot>> _availableSlots = {};
  late String userId;
  bool _isLoading = false;

  @override
  void initState() {
    super.initState();
    userId = FirebaseAuth.instance.currentUser!.uid;
    _fetchAvailableSlots();
  }

  void _fetchAvailableSlots() async {
    setState(() {
      _isLoading = true;
    });

    final slotsSnapshot = await FirebaseFirestore.instance
        .collection('users')
        .doc(userId)
        .collection('availableSlots')
        .get();

    setState(() {
      _availableSlots = {};
      for (var doc in slotsSnapshot.docs) {
        DateTime date = (doc['date'] as Timestamp).toDate();
        List<TimeSlot> slots = (doc['slots'] as List)
            .map((slot) => TimeSlot(
                  TimeOfDay.fromDateTime(
                      (slot['startTime'] as Timestamp).toDate()),
                  TimeOfDay.fromDateTime((slot['endTime'] as Timestamp).toDate()),
                ))
            .toList();
        _availableSlots[DateTime(date.year, date.month, date.day)] = slots;
      }
      _isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Booking Calendar'),
        actions: [
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
            child: ElevatedButton(
              onPressed: _saveAllSlots,
              style: ElevatedButton.styleFrom(
                backgroundColor: Color(0xFF1A237E), // Dark blue background (Indigo 900)
                foregroundColor: Colors.white, // White text
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(20), // Rounded borders
                ),
                padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                elevation: 0, // No shadow
              ),
              child: Text(
                'Save',
                style: TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          ),
        ],
      ),
      body: _isLoading
          ? Center(child: CircularProgressIndicator())
          : Column(
              children: [
                TableCalendar(
                  firstDay: DateTime.utc(2023, 1, 1),
                  lastDay: DateTime.utc(2024, 12, 31),
                  focusedDay: _focusedDay,
                  calendarFormat: _calendarFormat,
                  selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
                  onDaySelected: _onDaySelected,
                  onFormatChanged: (format) {
                    if (_calendarFormat != format) {
                      setState(() {
                        _calendarFormat = format;
                      });
                    }
                  },
                ),
                SizedBox(height: 20),
                Expanded(
                  child: _selectedDay == null
                      ? Center(child: Text('Please select a day'))
                      : _buildTimeSlotsList(),
                ),
              ],
            ),
      floatingActionButton: FloatingActionButton(
        onPressed: _showAddTimeSlotDialog,
        child: Icon(Icons.add),
      ),
    );
  }

  void _onDaySelected(DateTime selectedDay, DateTime focusedDay) {
    setState(() {
      _selectedDay = DateTime(selectedDay.year, selectedDay.month, selectedDay.day);
      _focusedDay = focusedDay;
    });
  }

  Widget _buildTimeSlotsList() {
    List<TimeSlot> slots = _availableSlots[_selectedDay!] ?? [];
    return slots.isEmpty
        ? Center(child: Text('No available slots for this day'))
        : ListView.builder(
            itemCount: slots.length,
            itemBuilder: (context, index) {
              TimeSlot slot = slots[index];
              return ListTile(
                title: Text('${slot.startTime.format(context)} - ${slot.endTime.format(context)}'),
                trailing: IconButton(
                  icon: Icon(Icons.delete),
                  onPressed: () => _removeTimeSlot(slot),
                ),
              );
            },
          );
  }

  void _showAddTimeSlotDialog() {
    TimeOfDay startTime = TimeOfDay(hour: 9, minute: 0);
    TimeOfDay endTime = TimeOfDay(hour: 17, minute: 0);

    showDialog(
      context: context,
      builder: (context) => StatefulBuilder(
        builder: (context, setState) {
          return Dialog(
            backgroundColor: Colors.white,
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text(
                    'Add Time Schedule',
                    style: TextStyle(
                      fontSize: 20,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  SizedBox(height: 24),
                  Text('Start Time'),
                  TimeInputWidget(
                    initialTime: startTime,
                    onChanged: (time) => startTime = time,
                  ),
                  SizedBox(height: 16),
                  Text('End Time'),
                  TimeInputWidget(
                    initialTime: endTime,
                    onChanged: (time) => endTime = time,
                  ),
                  SizedBox(height: 24),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.end,
                    children: [
                      TextButton(
                        child: Text('Cancel'),
                        onPressed: () => Navigator.of(context).pop(),
                      ),
                      SizedBox(width: 8),
                      ElevatedButton(
                        child: Text('Add'),
                        onPressed: () {
                          _addTimeSlot(startTime, endTime);
                          Navigator.of(context).pop();
                        },
                      ),
                    ],
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }

  void _addTimeSlot(TimeOfDay startTime, TimeOfDay endTime) {
    if (_selectedDay == null) return;

    setState(() {
      _availableSlots.update(
        _selectedDay!,
        (value) => [...value, TimeSlot(startTime, endTime)],
        ifAbsent: () => [TimeSlot(startTime, endTime)],
      );
    });
  }

  void _removeTimeSlot(TimeSlot slot) {
    setState(() {
      _availableSlots[_selectedDay!]?.remove(slot);
    });
  }

  void _saveAllSlots() async {
    setState(() {
      _isLoading = true;
    });

    try {
      for (var entry in _availableSlots.entries) {
        final date = entry.key;
        final slots = entry.value;

        final docRef = FirebaseFirestore.instance
            .collection('users')
            .doc(userId)
            .collection('availableSlots')
            .doc(date.toIso8601String().split('T')[0]);

        if (slots.isEmpty) {
          await docRef.delete();
        } else {
          await docRef.set({
            'date': Timestamp.fromDate(date),
            'slots': slots.map((slot) => {
              'startTime': Timestamp.fromDate(DateTime(
                  date.year, date.month, date.day, slot.startTime.hour, slot.startTime.minute)),
              'endTime': Timestamp.fromDate(DateTime(
                  date.year, date.month, date.day, slot.endTime.hour, slot.endTime.minute)),
            }).toList(),
          });
        }
      }

      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('All slots saved successfully')),
      );
    } catch (e) {
      print('Error saving slots: $e');
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error saving slots')),
      );
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }
}

class TimeSlot {
  final TimeOfDay startTime;
  final TimeOfDay endTime;

  TimeSlot(this.startTime, this.endTime);
}

class TimeInputWidget extends StatefulWidget {
  final TimeOfDay initialTime;
  final ValueChanged<TimeOfDay> onChanged;

  TimeInputWidget({required this.initialTime, required this.onChanged});

  @override
  _TimeInputWidgetState createState() => _TimeInputWidgetState();
}

class _TimeInputWidgetState extends State<TimeInputWidget> {
  late TextEditingController _hourController;
  late TextEditingController _minuteController;

  @override
  void initState() {
    super.initState();
    _hourController = TextEditingController(text: widget.initialTime.hour.toString().padLeft(2, '0'));
    _minuteController = TextEditingController(text: widget.initialTime.minute.toString().padLeft(2, '0'));
  }

  @override
  void dispose() {
    _hourController.dispose();
    _minuteController.dispose();
    super.dispose();
  }

  void _updateTime() {
    int hour = int.tryParse(_hourController.text) ?? 0;
    int minute = int.tryParse(_minuteController.text) ?? 0;
    widget.onChanged(TimeOfDay(hour: hour.clamp(0, 23), minute: minute.clamp(0, 59)));
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        SizedBox(
          width: 50,
          child: TextField(
            controller: _hourController,
            keyboardType: TextInputType.number,
            textAlign: TextAlign.center,
            inputFormatters: [
              FilteringTextInputFormatter.digitsOnly,
              LengthLimitingTextInputFormatter(2),
            ],
            onChanged: (value) => _updateTime(),
            decoration: InputDecoration(
              border: OutlineInputBorder(),
              contentPadding: EdgeInsets.symmetric(vertical: 8, horizontal: 4),
            ),
          ),
        ),
        Text(' : ', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
        SizedBox(
          width: 50,
          child: TextField(
            controller: _minuteController,
            keyboardType: TextInputType.number,
            textAlign: TextAlign.center,
            inputFormatters: [
              FilteringTextInputFormatter.digitsOnly,
              LengthLimitingTextInputFormatter(2),
            ],
            onChanged: (value) => _updateTime(),
            decoration: InputDecoration(
              border: OutlineInputBorder(),
              contentPadding: EdgeInsets.symmetric(vertical: 8, horizontal: 4),
            ),
          ),
        ),
      ],
    );
  }
}

这是用户显示数据的第二个

class BookingCalendarWidget extends StatefulWidget {
  final String serviceProviderId;
  final String serviceName;
  final String servicePrice;

  BookingCalendarWidget({
    required this.serviceProviderId,
    required this.serviceName,
    required this.servicePrice,
  });

  @override
  _BookingCalendarWidgetState createState() => _BookingCalendarWidgetState();
}

class _BookingCalendarWidgetState extends State<BookingCalendarWidget> {
  CalendarFormat _calendarFormat = CalendarFormat.month;
  DateTime _focusedDay = DateTime.now();
  DateTime? _selectedDay;
  Map<DateTime, List<TimeSlot>> _availableSlots = {};
  bool _isLoading = true;
  String _statusMessage = '';

  @override
  void initState() {
    super.initState();
    _fetchAvailableSlots();
  }

  void _fetchAvailableSlots() async {
    setState(() {
      _statusMessage = 'Fetching available slots...';
    });

    print("Fetching available slots for provider: ${widget.serviceProviderId}");
    try {
      final snapshot = await FirebaseFirestore.instance
          .collection("Users")
          .doc(widget.serviceProviderId)
          .collection("availableSlots")
          .get();

      print("Number of documents fetched: ${snapshot.docs.length}");

      Map<DateTime, List<TimeSlot>> slots = {};

      for (var doc in snapshot.docs) {
        print("Processing document: ${doc.id}");
        final data = doc.data();
        print("Document data: $data");
        
        final date = DateTime.parse(doc.id);
        print("Date: $date");

        final List<dynamic> slotsData = data['slots'] ?? [];
        print("Slots data: $slotsData");

        slots[date] = slotsData.map((slot) {
          final start = (slot['start'] as Timestamp).toDate();
          final end = (slot['end'] as Timestamp).toDate();
          return TimeSlot(
            start: start,
            end: end,
            serviceName: widget.serviceName,
            servicePrice: widget.servicePrice,
          );
        }).toList();

        print("Processed slots for $date: ${slots[date]}");
      }

      print("All processed slots: $slots");

      setState(() {
        _availableSlots = slots;
        _isLoading = false;
        if (_availableSlots.isEmpty) {
          _statusMessage = 'No available slots found for this service provider.';
        } else {
          _statusMessage = '';
        }
      });
    } catch (e) {
      print("Error fetching available slots: $e");
      setState(() {
        _isLoading = false;
        _statusMessage = 'Error fetching available slots: $e';
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    print("Building widget. isLoading: $_isLoading, statusMessage: $_statusMessage");
    return Scaffold(
      appBar: AppBar(
        title: Text('Book ${widget.serviceName}'),
      ),
      body: _isLoading
          ? Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  CircularProgressIndicator(),
                  SizedBox(height: 16),
                  Text(_statusMessage),
                ],
              ),
            )
          : Column(
              children: [
                TableCalendar(
                  firstDay: DateTime.now(),
                  lastDay: DateTime.now().add(Duration(days: 365)),
                  focusedDay: _focusedDay,
                  calendarFormat: _calendarFormat,
                  selectedDayPredicate: (day) {
                    return isSameDay(_selectedDay, day);
                  },
                  onDaySelected: (selectedDay, focusedDay) {
                    setState(() {
                      _selectedDay = selectedDay;
                      _focusedDay = focusedDay;
                    });
                  },
                  onFormatChanged: (format) {
                    setState(() {
                      _calendarFormat = format;
                    });
                  },
                  eventLoader: (day) {
                    return _availableSlots[day] ?? [];
                  },
                ),
                Expanded(
                  child: _selectedDay == null
                      ? Center(child: Text('Select a day to see available slots'))
                      : _buildTimeSlotList(),
                ),
              ],
            ),
    );
  }

  Widget _buildTimeSlotList() {
    print("Building time slot list for selected day: $_selectedDay");
    final slots = _availableSlots[_selectedDay!] ?? [];
    print("Slots for selected day: $slots");
    if (slots.isEmpty) {
      return Center(child: Text('No available slots for this day'));
    }
    return ListView.builder(
      itemCount: slots.length,
      itemBuilder: (context, index) {
        final slot = slots[index];
        print("Rendering slot: ${slot.start} - ${slot.end}");
        return ListTile(
          title: Text('${_formatTime(slot.start)} - ${_formatTime(slot.end)}'),
          subtitle: Text('${slot.serviceName} - ${slot.servicePrice}'),
          onTap: () => _confirmBooking(slot),
        );
      },
    );
  }

  String _formatTime(DateTime time) {
    return DateFormat('HH:mm').format(time);
  }

  void _confirmBooking(TimeSlot slot) {
    // ... (keep the existing _confirmBooking method)
  }
}

class TimeSlot {
  final DateTime start;
  final DateTime end;
  final String serviceName;
  final String servicePrice;

  TimeSlot({
    required this.start,
    required this.end,
    required this.serviceName,
    required this.servicePrice,
  });
}
flutter
1个回答
0
投票

您不能期望此代码:

 @override
  void initState() {
    super.initState();
    _fetchAvailableSlots();
  }

在 build() 开始之前完成,因此您将获得部分结果。 您将需要使用某种 FutureBuilder 在返回数据时进行加载显示。

© www.soinside.com 2019 - 2024. All rights reserved.