Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions frontend2/lib/apis/room_cleaning/room_cleaning_api.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import '../../constants/endpoint.dart';

class RoomCleaningApi {
// Dev-only: uses local emulator base URL from endpoint.dart.
static const bool isDev = true;

Future<String?> _getToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('access_token');
}

// uncomment this when the backend is deployed
Future<List<dynamic>> fetchSlots(
String hostelId, String weekDay) async {

final response = await http.get(
Uri.parse(
"$baseUrl/room-cleaning/slots?hostelId=$hostelId&weekDay=$weekDay"),
headers: {
"Content-Type": "application/json"
},
);

print("STATUS: ${response.statusCode}");
print("BODY: ${response.body}");

if (response.headers['content-type']?.contains('application/json') ?? false) {
final data = json.decode(response.body);

// Handle both possible formats safely
if (data is List) {
return data;
} else if (data is Map && data['slots'] != null) {
return data['slots'];
} else {
return [];
}
} else {
throw Exception("Non JSON response from slots API");
}
}


// Future<List<dynamic>> fetchSlots(
// String hostelId, String weekDay) async {
//
// await Future.delayed(Duration(seconds: 1));
//
// return [
// {
// "_id": "1",
// "startTime": "2026-02-15T08:00:00.000Z",
// "endTime": "2026-02-15T09:00:00.000Z",
// "availableSlots": 3,
// "maxSlots": 5,
// },
// {
// "_id": "2",
// "startTime": "2026-02-15T09:00:00.000Z",
// "endTime": "2026-02-15T10:00:00.000Z",
// "availableSlots": 0,
// "maxSlots": 5,
// },
// {
// "_id": "3",
// "startTime": "2026-02-15T10:00:00.000Z",
// "endTime": "2026-02-15T11:00:00.000Z",
// "availableSlots": 2,
// "maxSlots": 5,
// },
// ];
// }



Future<Map<String, dynamic>> bookSlot(
String slotId, String requestedDate, String notes) async {

final token = await _getToken();
print("TOKEN: $token");

final response = await http.post(
Uri.parse("$baseUrl/room-cleaning/booking/request"),
headers: {
"Authorization": "Bearer $token",
"Content-Type": "application/json",
if (isDev) "x-dev-guest": "1",
},
body: json.encode({
"slotId": slotId,
"requestedDate": requestedDate,
"notes": notes,
}),
);

print("STATUS CODE: ${response.statusCode}");
print("RAW BODY: ${response.body}");

if (response.headers['content-type']?.contains('application/json') ?? false) {
return json.decode(response.body);
} else {
throw Exception("Server returned non-JSON response");
}
}


Future<Map<String, dynamic>> cancelBooking(
String bookingId) async {
final token = await _getToken();

final response = await http.post(
Uri.parse(
"$baseUrl/room-cleaning/booking/cancel"),
headers: {
"Authorization": "Bearer $token",
"Content-Type": "application/json",
if (isDev) "x-dev-guest": "1",
},
body: json.encode({
"bookingId": bookingId,
}),
);

print("STATUS: ${response.statusCode}");
print("BODY: ${response.body}");
if (response.headers['content-type']?.contains('application/json') ?? false) {
return json.decode(response.body);
} else {
throw Exception("Server returned non-JSON response");
}
}

Future<List<dynamic>> getMyBookings() async {
final token = await _getToken();

final response = await http.get(
Uri.parse(
"$baseUrl/room-cleaning/booking/my"),
headers: {
"Authorization": "Bearer $token",
"Content-Type": "application/json",
if (isDev) "x-dev-guest": "1",
},
);

if (response.headers['content-type']?.contains('application/json') ?? false) {
final data = json.decode(response.body);
return data['bookings'] ?? [];
} else {
throw Exception("Server returned non-JSON response");
}
}
}
2 changes: 1 addition & 1 deletion frontend2/lib/constants/endpoint.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const String baseUrl = "https://hab.codingclub.in/api";
const String baseUrl = "http://10.200.242.48:3000/api";
const String authUrl = "https://hab.codingclub.in/api";

class NotificationEndpoints {
Expand Down
2 changes: 2 additions & 0 deletions frontend2/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:frontend2/apis/mess/user_mess_info.dart';
import 'package:frontend2/apis/users/user.dart';
import 'package:frontend2/providers/feedback_provider.dart';
import 'package:frontend2/providers/hostels.dart';
import 'package:frontend2/providers/room_cleaning_provider.dart';
import 'package:frontend2/screens/main_navigation_screen.dart';
import 'package:frontend2/screens/initial_setup_screen.dart';
import 'package:frontend2/screens/login_screen.dart';
Expand Down Expand Up @@ -70,6 +71,7 @@ Future<void> main() async {
providers: [
ChangeNotifierProvider(create: (_) => MessInfoProvider()),
ChangeNotifierProvider(create: (_) => FeedbackProvider()),
ChangeNotifierProvider(create: (_) => RoomCleaningProvider(),),
],
child: MyApp(isLoggedIn: asLoggedIn, updateRequired: updateRequired),
),
Expand Down
35 changes: 35 additions & 0 deletions frontend2/lib/models/room_cleaning_slot.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class RoomCleaningSlot {
final String id;
final String weekDay;
final DateTime startTime;
final DateTime endTime;
final int availableSlots;

RoomCleaningSlot({
required this.id,
required this.weekDay,
required this.startTime,
required this.endTime,
required this.availableSlots,
});

factory RoomCleaningSlot.fromJson(
Map<String, dynamic> json) {
final dynamic rawId = json['id'] ?? json['_id'];
final String id = rawId?.toString() ?? "";
final dynamic rawAvailable = json['availableSlots'];
final int availableSlots = rawAvailable is int
? rawAvailable
: int.tryParse(rawAvailable?.toString() ?? "") ?? 0;

return RoomCleaningSlot(
id: id,
weekDay: json['weekDay']?.toString() ?? "",
startTime: DateTime.parse(
json['startTime']?.toString() ?? DateTime.now().toIso8601String()),
endTime: DateTime.parse(
json['endTime']?.toString() ?? DateTime.now().toIso8601String()),
availableSlots: availableSlots,
);
}
}
133 changes: 133 additions & 0 deletions frontend2/lib/providers/room_cleaning_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../apis/room_cleaning/room_cleaning_api.dart';
import '../models/room_cleaning_slot.dart';

class RoomCleaningProvider extends ChangeNotifier {
final RoomCleaningApi _api = RoomCleaningApi();

List<RoomCleaningSlot> slots = [];
bool isLoading = false;
String? errorMessage;
List<dynamic> myBookings = [];
bool isBookingsLoading = false;
String? bookingsError;
bool localBookingsLoaded = false;
Set<String> localBookedDates = {};

Future<void> fetchSlots(
String hostelId, String weekDay) async {
isLoading = true;
errorMessage = null;
notifyListeners();

try {
final rawSlots = await _api.fetchSlots(hostelId, weekDay);
slots = rawSlots.map((e) => RoomCleaningSlot.fromJson(e)).toList();
} catch (e) {
slots = [];
errorMessage = e.toString();
} finally {
isLoading = false;
notifyListeners();
}
}

Future<Map<String, dynamic>> bookSlot(
String slotId,
String date,
String notes) async {
return await _api.bookSlot(
slotId, date, notes);
}

Future<Map<String, dynamic>> cancelBooking(
String bookingId) async {
return await _api.cancelBooking(
bookingId);
}

Future<List<dynamic>> getMyBookings() async {
return await _api.getMyBookings();
}

String _dateKey(DateTime date) {
return "${date.year.toString().padLeft(4, '0')}-"
"${date.month.toString().padLeft(2, '0')}-"
"${date.day.toString().padLeft(2, '0')}";
}

Future<void> loadLocalBookings() async {
if (!RoomCleaningApi.isDev) return;
if (localBookingsLoaded) return;
final prefs = await SharedPreferences.getInstance();
final saved = prefs.getStringList("room_cleaning_booked_dates") ?? [];
localBookedDates = saved.toSet();
localBookingsLoaded = true;
notifyListeners();
}

bool hasLocalBookingForDate(DateTime date) {
return localBookedDates.contains(_dateKey(date));
}

Future<void> fetchMyBookings() async {
isBookingsLoading = true;
bookingsError = null;
notifyListeners();

try {
myBookings = await _api.getMyBookings();
} catch (e) {
myBookings = [];
bookingsError = e.toString();
} finally {
isBookingsLoading = false;
notifyListeners();
}
}

void addLocalBooking({
required DateTime requestedDate,
RoomCleaningSlot? slot,
}) {
if (!RoomCleaningApi.isDev) {
return;
}
final booking = {
'_id': 'local-booking-${DateTime.now().millisecondsSinceEpoch}',
'requestedDate': requestedDate.toIso8601String(),
'status': 'confirmed',
'slot': slot == null
? null
: {
'_id': slot.id,
'weekDay': slot.weekDay,
'startTime': slot.startTime.toIso8601String(),
'endTime': slot.endTime.toIso8601String(),
},
};
myBookings = [booking, ...myBookings];
localBookedDates.add(_dateKey(requestedDate));
SharedPreferences.getInstance().then((prefs) {
prefs.setStringList(
"room_cleaning_booked_dates",
localBookedDates.toList(),
);
});
if (slot != null) {
slots = slots.map((s) {
if (s.id != slot.id) return s;
final nextAvailable = (s.availableSlots - 1);
return RoomCleaningSlot(
id: s.id,
weekDay: s.weekDay,
startTime: s.startTime,
endTime: s.endTime,
availableSlots: nextAvailable < 0 ? 0 : nextAvailable,
);
}).toList();
}
notifyListeners();
}
}
Loading