2025-08-28 01:11:19 +07:00
|
|
|
import 'package:auto_route/auto_route.dart';
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
|
|
|
|
|
import '../../../common/theme/theme.dart';
|
2025-08-29 21:30:57 +07:00
|
|
|
import '../../../sample/sample_data.dart';
|
2025-08-28 01:11:19 +07:00
|
|
|
import '../../components/field/field.dart';
|
2025-08-29 16:07:01 +07:00
|
|
|
import '../../router/app_router.gr.dart';
|
2025-08-28 01:11:19 +07:00
|
|
|
import 'widgets/empty_merchant_card.dart';
|
|
|
|
|
import 'widgets/merchant_card.dart';
|
|
|
|
|
|
|
|
|
|
@RoutePage()
|
|
|
|
|
class MerchantPage extends StatefulWidget {
|
|
|
|
|
const MerchantPage({super.key});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
State<MerchantPage> createState() => _MerchantPageState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _MerchantPageState extends State<MerchantPage> {
|
|
|
|
|
final TextEditingController _searchController = TextEditingController();
|
|
|
|
|
final List<MerchantModel> _allMerchants = _generateMockMerchants();
|
|
|
|
|
List<MerchantModel> _filteredMerchants = [];
|
2025-08-29 21:02:57 +07:00
|
|
|
String? _selectedCategory;
|
|
|
|
|
late List<String> _categories;
|
2025-08-28 01:11:19 +07:00
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
|
|
|
|
_filteredMerchants = _allMerchants;
|
2025-08-29 21:02:57 +07:00
|
|
|
_categories = _getAllCategories();
|
2025-08-28 01:11:19 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void dispose() {
|
|
|
|
|
_searchController.dispose();
|
|
|
|
|
super.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-29 21:02:57 +07:00
|
|
|
List<String> _getAllCategories() {
|
|
|
|
|
final categories = _allMerchants
|
|
|
|
|
.map((merchant) => merchant.category)
|
|
|
|
|
.toSet()
|
|
|
|
|
.toList();
|
|
|
|
|
categories.sort();
|
|
|
|
|
return categories;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-28 01:11:19 +07:00
|
|
|
void _filterMerchants(String query) {
|
|
|
|
|
setState(() {
|
2025-08-29 21:02:57 +07:00
|
|
|
var merchants = _allMerchants;
|
|
|
|
|
|
|
|
|
|
// Filter by category first
|
|
|
|
|
if (_selectedCategory != null) {
|
|
|
|
|
merchants = merchants
|
|
|
|
|
.where((merchant) => merchant.category == _selectedCategory)
|
|
|
|
|
.toList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Then filter by search query
|
|
|
|
|
if (query.isNotEmpty) {
|
|
|
|
|
merchants = merchants
|
2025-08-28 01:11:19 +07:00
|
|
|
.where(
|
|
|
|
|
(merchant) =>
|
|
|
|
|
merchant.name.toLowerCase().contains(query.toLowerCase()) ||
|
|
|
|
|
merchant.category.toLowerCase().contains(query.toLowerCase()),
|
|
|
|
|
)
|
|
|
|
|
.toList();
|
|
|
|
|
}
|
2025-08-29 21:02:57 +07:00
|
|
|
|
|
|
|
|
_filteredMerchants = merchants;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _onCategorySelected(String? category) {
|
|
|
|
|
setState(() {
|
|
|
|
|
_selectedCategory = category;
|
2025-08-28 01:11:19 +07:00
|
|
|
});
|
2025-08-29 21:02:57 +07:00
|
|
|
_filterMerchants(_searchController.text);
|
2025-08-28 01:11:19 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
return Scaffold(
|
|
|
|
|
backgroundColor: AppColor.background,
|
|
|
|
|
appBar: AppBar(
|
2025-08-29 21:02:57 +07:00
|
|
|
title: const Text('Merchants'),
|
2025-08-28 01:11:19 +07:00
|
|
|
bottom: PreferredSize(
|
2025-08-29 21:02:57 +07:00
|
|
|
preferredSize: const Size.fromHeight(
|
|
|
|
|
130,
|
|
|
|
|
), // Increased height for filter chips
|
|
|
|
|
child: Column(
|
|
|
|
|
children: [
|
|
|
|
|
// Search Field
|
|
|
|
|
SearchTextField(
|
|
|
|
|
hintText: 'Search merchants...',
|
|
|
|
|
prefixIcon: Icons.search,
|
|
|
|
|
controller: _searchController,
|
|
|
|
|
// onChanged: _filterMerchants,
|
|
|
|
|
onClear: () {
|
|
|
|
|
_searchController.clear();
|
|
|
|
|
_filterMerchants('');
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
|
// Category Filter Chips
|
|
|
|
|
// Category Filter Chips - Updated with AppColor and opacity 1
|
|
|
|
|
Container(
|
|
|
|
|
height: 40,
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
|
|
|
child: ListView(
|
|
|
|
|
scrollDirection: Axis.horizontal,
|
|
|
|
|
children: [
|
|
|
|
|
// "All" filter chip
|
|
|
|
|
Padding(
|
|
|
|
|
padding: const EdgeInsets.only(right: 8),
|
|
|
|
|
child: FilterChip(
|
2025-08-29 21:30:57 +07:00
|
|
|
label: const Text('Semua'),
|
2025-08-29 21:02:57 +07:00
|
|
|
selected: _selectedCategory == null,
|
|
|
|
|
onSelected: (selected) {
|
|
|
|
|
_onCategorySelected(null);
|
|
|
|
|
},
|
|
|
|
|
selectedColor: AppColor.primary,
|
|
|
|
|
backgroundColor: AppColor.white,
|
|
|
|
|
checkmarkColor: AppColor.white,
|
|
|
|
|
labelStyle: TextStyle(
|
|
|
|
|
color: _selectedCategory == null
|
|
|
|
|
? AppColor.white
|
|
|
|
|
: AppColor.primary,
|
|
|
|
|
),
|
|
|
|
|
side: BorderSide(color: AppColor.primary, width: 1),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
// Category filter chips
|
|
|
|
|
..._categories.map(
|
|
|
|
|
(category) => Padding(
|
|
|
|
|
padding: const EdgeInsets.only(right: 8),
|
|
|
|
|
child: FilterChip(
|
|
|
|
|
label: Text(category),
|
|
|
|
|
selected: _selectedCategory == category,
|
|
|
|
|
onSelected: (selected) {
|
|
|
|
|
_onCategorySelected(selected ? category : null);
|
|
|
|
|
},
|
|
|
|
|
selectedColor: AppColor.primary,
|
|
|
|
|
backgroundColor: AppColor.white,
|
|
|
|
|
checkmarkColor: AppColor.white,
|
|
|
|
|
labelStyle: TextStyle(
|
|
|
|
|
color: _selectedCategory == category
|
|
|
|
|
? AppColor.white
|
|
|
|
|
: AppColor.primary,
|
|
|
|
|
),
|
|
|
|
|
side: BorderSide(color: AppColor.primary, width: 1),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(height: 8),
|
|
|
|
|
],
|
2025-08-28 01:11:19 +07:00
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
body: _filteredMerchants.isEmpty
|
|
|
|
|
? _buildEmptyState()
|
|
|
|
|
: Padding(
|
|
|
|
|
padding: const EdgeInsets.all(16),
|
|
|
|
|
child: GridView.builder(
|
|
|
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
|
|
|
|
crossAxisCount: 2,
|
|
|
|
|
crossAxisSpacing: 16,
|
|
|
|
|
mainAxisSpacing: 16,
|
|
|
|
|
childAspectRatio: 0.85,
|
|
|
|
|
),
|
|
|
|
|
itemCount: _filteredMerchants.length,
|
|
|
|
|
itemBuilder: (context, index) {
|
|
|
|
|
return _buildMerchantCard(_filteredMerchants[index]);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Widget _buildMerchantCard(MerchantModel merchant) {
|
|
|
|
|
return MerchantCard(
|
|
|
|
|
merchant: merchant,
|
|
|
|
|
onTap: () {
|
2025-08-29 16:07:01 +07:00
|
|
|
context.router.push(MerchantDetailRoute(merchant: merchant));
|
2025-08-28 01:11:19 +07:00
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Widget _buildEmptyState() {
|
|
|
|
|
return const EmptyMerchantCard();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static List<MerchantModel> _generateMockMerchants() {
|
2025-08-29 21:30:57 +07:00
|
|
|
return merchants;
|
2025-08-28 01:11:19 +07:00
|
|
|
}
|
|
|
|
|
}
|