fixed reserve list

This commit is contained in:
mohamadmahdi jebeli 2025-08-10 16:52:13 +03:30
parent 585cb91df5
commit 205a58359c
9 changed files with 240 additions and 24 deletions

View File

@ -55,4 +55,10 @@ class ApiConfig {
/// Body: {'Discount': discountId, 'User': userId}
/// Headers: {'Authorization': 'Bearer <token>'}
static const String addOrder = '$baseUrl/order/add';
// ========== Reservation Endpoints ==========
/// Endpoint to get reservations.
/// Method: GET
/// Headers: {'Authorization': 'Bearer <token>'}
static const String getReservations = '$baseUrl/reservation/get';
}

View File

@ -0,0 +1,76 @@
class ReservationEntity {
final String id;
final String shop;
final ReservationDiscount discount;
final String user;
final bool status;
ReservationEntity({
required this.id,
required this.shop,
required this.discount,
required this.user,
required this.status,
});
factory ReservationEntity.fromJson(Map<String, dynamic> json) {
return ReservationEntity(
id: json['ID'] ?? '',
shop: json['Shop'] ?? '',
discount: ReservationDiscount.fromJson(json['Discount'] ?? {}),
user: json['User'] ?? '',
status: json['Status'] ?? false,
);
}
}
class ReservationDiscount {
final String id;
final String name;
final String typeName;
final DateTime? endDate;
final List<ReservationImage> images;
ReservationDiscount({
required this.id,
required this.name,
this.endDate,
required this.images,
required this.typeName,
});
factory ReservationDiscount.fromJson(Map<String, dynamic> json) {
List<ReservationImage> _parseImages(dynamic imageList) {
if (imageList is List) {
return imageList.map((img) => ReservationImage.fromJson(img)).toList();
}
return [];
}
return ReservationDiscount(
id: json['ID'] ?? '',
name: json['Name'] ?? 'بدون نام',
endDate: json['EndDate'] != null ? DateTime.tryParse(json['EndDate']) : null,
images: _parseImages(json['Images']),
typeName: json['Type']?['Name'] ?? 'نامشخص',
);
}
}
class ReservationImage {
final String id;
final String url;
ReservationImage({
required this.id,
required this.url,
});
factory ReservationImage.fromJson(Map<String, dynamic> json) {
return ReservationImage(
id: json['_id'] ?? '',
url: json['Url'] ?? '',
);
}
}

View File

@ -79,7 +79,6 @@ class _BarcodeScannerPageState extends State<BarcodeScannerPage> {
if (await Vibration.hasVibrator() ?? false) {
Vibration.vibrate(duration: 200);
}
// Ensure you have a success sound file at this path
await _audioPlayer.play(AssetSource('sounds/short-success-sound-glockenspiel-treasure-video-game-6346.mp3'));
await showSuccessDialog(context, message: state.message);
Navigator.of(context).pop();

View File

@ -1,12 +1,31 @@
import 'dart:async'; // اضافه کردن کتابخانه برای استفاده از Timer
import 'package:business_panel/presentation/home/bloc/home_bloc.dart';
import 'dart:async';
import 'package:business_panel/presentation/reservation/bloc/reservation_bloc.dart';
import 'package:business_panel/presentation/widgets/active_discount_card.dart';
import 'package:business_panel/presentation/widgets/custom_app_bar_single.dart';
import 'package:business_panel/domain/entities/discount_entity.dart';
import 'package:business_panel/domain/entities/reservation_entity.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:business_panel/gen/assets.gen.dart';
extension ReservationToDiscount on ReservationEntity {
DiscountEntity toDiscountEntity() {
return DiscountEntity(
id: id,
name: discount.name,
shopName: 'فروشگاه',
images: discount.images.map((img) => img.url).toList(),
type: discount.typeName,
description: 'رزرو شده',
price: 0.0,
nPrice: 0.0,
startDate: null,
endDate: discount.endDate,
);
}
}
class ReserveManegmment extends StatefulWidget {
const ReserveManegmment({super.key});
@ -15,23 +34,35 @@ class ReserveManegmment extends StatefulWidget {
}
class _ReserveManegmmentState extends State<ReserveManegmment> {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => ReservationBloc()..add(FetchReservations()),
child: const _ReserveManegmmentView(),
);
}
}
class _ReserveManegmmentView extends StatefulWidget {
const _ReserveManegmmentView();
@override
State<_ReserveManegmmentView> createState() => _ReserveManegmmentViewState();
}
class _ReserveManegmmentViewState extends State<_ReserveManegmmentView> {
final TextEditingController _searchController = TextEditingController();
Timer? _debounce;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
context.read<HomeBloc>().add(FetchDiscounts());
}
});
}
@override
void dispose() {
_searchController.dispose();
_debounce?.cancel(); // کنسل کردن تایمر برای جلوگیری از نشت حافظه
_debounce?.cancel();
super.dispose();
}
@ -39,7 +70,7 @@ class _ReserveManegmmentState extends State<ReserveManegmment> {
if (_debounce?.isActive ?? false) _debounce!.cancel();
_debounce = Timer(const Duration(milliseconds: 500), () {
if (mounted) {
context.read<HomeBloc>().add(SearchDiscounts(query: query));
context.read<ReservationBloc>().add(SearchReservations(query: query));
}
});
}
@ -73,30 +104,31 @@ class _ReserveManegmmentState extends State<ReserveManegmment> {
),
contentPadding: const EdgeInsets.symmetric(vertical: 0),
),
onChanged: _onSearchChanged, // استفاده از متد جدید
onChanged: _onSearchChanged,
),
),
Expanded(
child: BlocBuilder<HomeBloc, HomeState>(
child: BlocBuilder<ReservationBloc, ReservationState>(
builder: (context, state) {
if (state is HomeError) {
if (state is ReservationError) {
return Center(child: Text('خطا: ${state.message}'));
}
if (state is HomeLoaded) {
if (state.discounts.isEmpty) {
return const Center(child: Text("هیچ تخفیفی با این مشخصات یافت نشد."));
if (state is ReservationLoaded) {
if (state.reservations.isEmpty) {
return const Center(child: Text("هیچ رزروی با این مشخصات یافت نشد."));
}
return RefreshIndicator(
onRefresh: () async {
context.read<HomeBloc>().add(FetchDiscounts());
context.read<ReservationBloc>().add(FetchReservations());
_searchController.clear();
},
child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: state.discounts.length,
itemCount: state.reservations.length,
itemBuilder: (context, index) {
final discount = state.discounts[index];
final reservation = state.reservations[index];
final discount = reservation.toDiscountEntity();
return ActiveDiscountCard(discount: discount);
},
),

View File

@ -0,0 +1,75 @@
import 'package:bloc/bloc.dart';
import 'package:business_panel/core/config/api_config.dart';
import 'package:business_panel/core/services/token_storage_service.dart';
import 'package:business_panel/core/utils/logging_interceptor.dart';
import 'package:business_panel/domain/entities/reservation_entity.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
part 'reservation_event.dart';
part 'reservation_state.dart';
class ReservationBloc extends Bloc<ReservationEvent, ReservationState> {
final Dio _dio = Dio();
final TokenStorageService _tokenStorage = TokenStorageService();
ReservationBloc() : super(ReservationInitial()) {
_dio.interceptors.add(LoggingInterceptor());
on<FetchReservations>((event, emit) async {
await _fetchReservations(emit);
});
on<SearchReservations>((event, emit) async {
await _fetchReservations(emit, searchQuery: event.query);
});
}
Future<void> _fetchReservations(Emitter<ReservationState> emit, {String? searchQuery}) async {
emit(ReservationLoading());
try {
final token = await _tokenStorage.getAccessToken();
if (token == null || token.isEmpty) {
emit(ReservationError("خطای احراز هویت. لطفا دوباره وارد شوید."));
return;
}
String url = ApiConfig.getReservations;
if (searchQuery != null && searchQuery.isNotEmpty) {
url = '$url?search=$searchQuery';
}
final response = await _dio.get(
url,
options: Options(
headers: {'Authorization': 'Bearer $token'},
),
);
if (response.statusCode == 200 && response.data['data'] != null) {
final List<dynamic> data = response.data['data']['items'] ?? [];
final reservations = data
.map((json) => ReservationEntity.fromJson(json))
.where((reservation) {
if (reservation.discount.endDate == null) return true;
return reservation.discount.endDate!.isAfter(DateTime.now());
})
.toList();
emit(ReservationLoaded(reservations));
} else {
emit(ReservationError(response.data['message'] ?? 'خطا در دریافت اطلاعات'));
}
} on DioException catch (e) {
if (kDebugMode) {
print('DioException in ReservationBloc: ${e.response?.data}');
}
emit(ReservationError(e.response?.data['message'] ?? 'خطای شبکه'));
} catch (e, stackTrace) {
if (kDebugMode) {
print('Error in ReservationBloc: $e');
print(stackTrace);
}
emit(ReservationError('خطای پیش‌بینی نشده رخ داد: ${e.toString()}'));
}
}
}

View File

@ -0,0 +1,11 @@
part of 'reservation_bloc.dart';
abstract class ReservationEvent {}
class FetchReservations extends ReservationEvent {}
class SearchReservations extends ReservationEvent {
final String query;
SearchReservations({required this.query});
}

View File

@ -0,0 +1,19 @@
part of 'reservation_bloc.dart';
abstract class ReservationState {}
class ReservationInitial extends ReservationState {}
class ReservationLoading extends ReservationState {}
class ReservationLoaded extends ReservationState {
final List<ReservationEntity> reservations;
ReservationLoaded(this.reservations);
}
class ReservationError extends ReservationState {
final String message;
ReservationError(this.message);
}

View File

@ -148,7 +148,7 @@ class ActiveDiscountCard extends StatelessWidget {
],
),
const SizedBox(height: 16),
InkWell( // <-- ویجت را در InkWell قرار دهید
InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(

View File

@ -1,5 +1,3 @@
// lib/presentation/widgets/analytics_discount_card.dart
import 'package:business_panel/core/config/app_colors.dart';
import 'package:business_panel/domain/entities/discount_entity.dart';
import 'package:business_panel/gen/assets.gen.dart';
@ -99,7 +97,7 @@ class AnalyticsDiscountCard extends StatelessWidget {
textColor: Colors.grey.shade600),
const SizedBox(height: 5),
if (discount.endDate == null)
_buildStatusText('تاریخ نامعتبر', Colors.orange)
_buildStatusText('تاریخ نامعتبر', Colors.orange)
else if (remaining.isNegative)
_buildExpiredDateRange()
else
@ -228,7 +226,7 @@ class AnalyticsDiscountCard extends StatelessWidget {
if (value == true) {
final bloc = context.read<DiscountManagementBloc>();
bloc.add(
FetchManagedDiscounts(status: 1)); // Defaulting to active
FetchManagedDiscounts(status: 1));
}
});
},