fixed reserve list
This commit is contained in:
parent
585cb91df5
commit
205a58359c
|
|
@ -55,4 +55,10 @@ class ApiConfig {
|
||||||
/// Body: {'Discount': discountId, 'User': userId}
|
/// Body: {'Discount': discountId, 'User': userId}
|
||||||
/// Headers: {'Authorization': 'Bearer <token>'}
|
/// Headers: {'Authorization': 'Bearer <token>'}
|
||||||
static const String addOrder = '$baseUrl/order/add';
|
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) {
|
if (await Vibration.hasVibrator() ?? false) {
|
||||||
Vibration.vibrate(duration: 200);
|
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 _audioPlayer.play(AssetSource('sounds/short-success-sound-glockenspiel-treasure-video-game-6346.mp3'));
|
||||||
await showSuccessDialog(context, message: state.message);
|
await showSuccessDialog(context, message: state.message);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,31 @@
|
||||||
import 'dart:async'; // اضافه کردن کتابخانه برای استفاده از Timer
|
import 'dart:async';
|
||||||
import 'package:business_panel/presentation/home/bloc/home_bloc.dart';
|
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/active_discount_card.dart';
|
||||||
import 'package:business_panel/presentation/widgets/custom_app_bar_single.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/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:business_panel/gen/assets.gen.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 {
|
class ReserveManegmment extends StatefulWidget {
|
||||||
const ReserveManegmment({super.key});
|
const ReserveManegmment({super.key});
|
||||||
|
|
||||||
|
|
@ -15,23 +34,35 @@ class ReserveManegmment extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ReserveManegmmentState extends State<ReserveManegmment> {
|
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();
|
final TextEditingController _searchController = TextEditingController();
|
||||||
Timer? _debounce;
|
Timer? _debounce;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
if (mounted) {
|
|
||||||
context.read<HomeBloc>().add(FetchDiscounts());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_searchController.dispose();
|
_searchController.dispose();
|
||||||
_debounce?.cancel(); // کنسل کردن تایمر برای جلوگیری از نشت حافظه
|
_debounce?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,7 +70,7 @@ class _ReserveManegmmentState extends State<ReserveManegmment> {
|
||||||
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
||||||
_debounce = Timer(const Duration(milliseconds: 500), () {
|
_debounce = Timer(const Duration(milliseconds: 500), () {
|
||||||
if (mounted) {
|
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),
|
contentPadding: const EdgeInsets.symmetric(vertical: 0),
|
||||||
),
|
),
|
||||||
onChanged: _onSearchChanged, // استفاده از متد جدید
|
onChanged: _onSearchChanged,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: BlocBuilder<HomeBloc, HomeState>(
|
child: BlocBuilder<ReservationBloc, ReservationState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is HomeError) {
|
if (state is ReservationError) {
|
||||||
return Center(child: Text('خطا: ${state.message}'));
|
return Center(child: Text('خطا: ${state.message}'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state is HomeLoaded) {
|
if (state is ReservationLoaded) {
|
||||||
if (state.discounts.isEmpty) {
|
if (state.reservations.isEmpty) {
|
||||||
return const Center(child: Text("هیچ تخفیفی با این مشخصات یافت نشد."));
|
return const Center(child: Text("هیچ رزروی با این مشخصات یافت نشد."));
|
||||||
}
|
}
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
context.read<HomeBloc>().add(FetchDiscounts());
|
context.read<ReservationBloc>().add(FetchReservations());
|
||||||
_searchController.clear();
|
_searchController.clear();
|
||||||
},
|
},
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
itemCount: state.discounts.length,
|
itemCount: state.reservations.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final discount = state.discounts[index];
|
final reservation = state.reservations[index];
|
||||||
|
final discount = reservation.toDiscountEntity();
|
||||||
return ActiveDiscountCard(discount: discount);
|
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),
|
const SizedBox(height: 16),
|
||||||
InkWell( // <-- ویجت را در InkWell قرار دهید
|
InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
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/core/config/app_colors.dart';
|
||||||
import 'package:business_panel/domain/entities/discount_entity.dart';
|
import 'package:business_panel/domain/entities/discount_entity.dart';
|
||||||
import 'package:business_panel/gen/assets.gen.dart';
|
import 'package:business_panel/gen/assets.gen.dart';
|
||||||
|
|
@ -228,7 +226,7 @@ class AnalyticsDiscountCard extends StatelessWidget {
|
||||||
if (value == true) {
|
if (value == true) {
|
||||||
final bloc = context.read<DiscountManagementBloc>();
|
final bloc = context.read<DiscountManagementBloc>();
|
||||||
bloc.add(
|
bloc.add(
|
||||||
FetchManagedDiscounts(status: 1)); // Defaulting to active
|
FetchManagedDiscounts(status: 1));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue