fixed reserve list
This commit is contained in:
parent
585cb91df5
commit
205a58359c
|
|
@ -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';
|
||||
}
|
||||
|
|
@ -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'] ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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()}'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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});
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -148,7 +148,7 @@ class ActiveDiscountCard extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
InkWell( // <-- ویجت را در InkWell قرار دهید
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue