1056 lines
40 KiB
Dart
1056 lines
40 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'dart:convert';
|
|
import 'dart:async';
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import '../config.dart';
|
|
|
|
import 'profile.dart';
|
|
import '../staf/home/payments.dart';
|
|
|
|
class BerandaPage extends StatefulWidget {
|
|
final Map<String, dynamic> userData;
|
|
const BerandaPage({super.key, required this.userData});
|
|
|
|
@override
|
|
State<BerandaPage> createState() => _BerandaPageState();
|
|
}
|
|
|
|
class _BerandaPageState extends State<BerandaPage> {
|
|
int _selectedIndex = 0;
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: const Color(0xFFF7F9FB), // Matches staff background
|
|
body: IndexedStack(
|
|
index: _selectedIndex,
|
|
children: [
|
|
HomeDriverTab(userData: widget.userData),
|
|
ProfilePage(userData: widget.userData),
|
|
],
|
|
),
|
|
bottomNavigationBar: _buildPersistentBottomNav(),
|
|
);
|
|
}
|
|
|
|
Widget _buildPersistentBottomNav() {
|
|
return Container(
|
|
height: 64 + MediaQuery.of(context).padding.bottom,
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFFFFFFF).withOpacity(0.95),
|
|
border:
|
|
const Border(top: BorderSide(color: Color(0xFFE6E8EA), width: 1.0)),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.03),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, -1))
|
|
],
|
|
),
|
|
child: SafeArea(
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: [
|
|
_navItem(Icons.home, "Beranda", _selectedIndex == 0, () {
|
|
setState(() => _selectedIndex = 0);
|
|
}),
|
|
_navItem(Icons.person, "Profil", _selectedIndex == 1, () {
|
|
setState(() => _selectedIndex = 1);
|
|
}),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _navItem(
|
|
IconData icon, String label, bool isActive, VoidCallback onTap) {
|
|
return InkWell(
|
|
onTap: onTap,
|
|
splashColor: Colors.transparent,
|
|
highlightColor: Colors.transparent,
|
|
child: Container(
|
|
width: 64,
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
icon,
|
|
size: 24,
|
|
color: isActive
|
|
? const Color(0xFF0058BE)
|
|
: const Color(0xFF424754).withOpacity(0.6),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
label,
|
|
style: GoogleFonts.inter(
|
|
fontSize: 10,
|
|
fontWeight: isActive ? FontWeight.w700 : FontWeight.w500,
|
|
color: isActive
|
|
? const Color(0xFF0058BE)
|
|
: const Color(0xFF424754).withOpacity(0.6),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class HomeDriverTab extends StatefulWidget {
|
|
final Map<String, dynamic> userData;
|
|
const HomeDriverTab({super.key, required this.userData});
|
|
|
|
@override
|
|
State<HomeDriverTab> createState() => _HomeDriverTabState();
|
|
}
|
|
|
|
class _HomeDriverTabState extends State<HomeDriverTab>
|
|
with SingleTickerProviderStateMixin {
|
|
final Color bgLight = const Color(0xFFF7F9FB);
|
|
final Color surfaceColor = const Color(0xFFFFFFFF);
|
|
final Color primaryColor = const Color(0xFF0058BE);
|
|
final Color primaryDark = const Color(0xFF004395);
|
|
final Color textDark = const Color(0xFF191C1E);
|
|
final Color textGray = const Color(0xFF424754);
|
|
final Color borderLight = const Color(0xFFE6E8EA);
|
|
|
|
final Color primary = const Color(0xFF0058BE);
|
|
final Color onPrimary = const Color(0xFFFFFFFF);
|
|
final Color primaryContainer = const Color(0xFF2170E4);
|
|
final Color surfaceContainerLowest = const Color(0xFFFFFFFF);
|
|
final Color surfaceContainerLow = const Color(0xFFF2F4F6);
|
|
final Color surfaceContainerHigh = const Color(0xFFE6E8EA);
|
|
final Color onSurface = const Color(0xFF191C1E);
|
|
final Color onSurfaceVariant = const Color(0xFF424754);
|
|
final Color outlineVariant = const Color(0xFFC2C6D6);
|
|
final Color outline = const Color(0xFF727785);
|
|
final Color secondaryContainer = const Color(0xFFC3ECD7);
|
|
final Color onSecondaryContainer = const Color(0xFF476C5B);
|
|
|
|
List<dynamic> tasks = [];
|
|
Set<String> _sentMessages = {};
|
|
bool isLoading = true;
|
|
late TabController _tabController;
|
|
final Set<String> _submittingTaskIds = {};
|
|
final Set<String> _sendingWhatsAppTaskIds = {};
|
|
|
|
String formatHarga(dynamic value) {
|
|
if (value == null) return "0";
|
|
String val = value.toString();
|
|
RegExp reg = RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))');
|
|
return val.replaceAllMapped(reg, (Match m) => '${m[1]}.');
|
|
}
|
|
|
|
Widget _buildPaymentBadge(Map<String, dynamic> task) {
|
|
final payMethod = (task['payment_method'] ?? "").toString().toUpperCase();
|
|
final cashRec = num.tryParse(task['cash_received']?.toString() ?? "0") ?? 0;
|
|
final isUnpaid = payMethod == "BAYAR NANTI" && cashRec == 0;
|
|
|
|
if (isUnpaid) {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFFFDAD6),
|
|
borderRadius: BorderRadius.circular(999),
|
|
border: Border.all(color: const Color(0xFF93000A).withOpacity(0.2)),
|
|
),
|
|
child: Text(
|
|
"BELUM BAYAR",
|
|
style: GoogleFonts.inter(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.bold,
|
|
color: const Color(0xFF93000A),
|
|
),
|
|
),
|
|
);
|
|
} else {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: secondaryContainer,
|
|
borderRadius: BorderRadius.circular(999),
|
|
border: Border.all(color: onSecondaryContainer.withOpacity(0.2)),
|
|
),
|
|
child: Text(
|
|
"LUNAS",
|
|
style: GoogleFonts.inter(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.bold,
|
|
color: onSecondaryContainer,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
Timer? _refreshTimer;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_tabController = TabController(length: 2, vsync: this);
|
|
_loadSentMessages();
|
|
_fetchTasks();
|
|
|
|
_refreshTimer = Timer.periodic(const Duration(seconds: 3), (_) {
|
|
_fetchTasks(showLoading: false);
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_refreshTimer?.cancel();
|
|
_tabController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
// Mengambil data pesan terkirim dari memori HP
|
|
Future<void> _loadSentMessages() async {
|
|
try {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final List<String>? sentList = prefs.getStringList('sent_wa_messages_v2');
|
|
if (sentList != null) {
|
|
setState(() {
|
|
_sentMessages = sentList.toSet();
|
|
});
|
|
}
|
|
} catch (e) {
|
|
// ignore: empty_catches
|
|
}
|
|
}
|
|
|
|
// Menyimpan data pesan terkirim ke memori HP
|
|
Future<void> _saveSentMessage(String taskId) async {
|
|
try {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
setState(() {
|
|
_sentMessages.add(taskId);
|
|
});
|
|
await prefs.setStringList('sent_wa_messages_v2', _sentMessages.toList());
|
|
} catch (e) {
|
|
// ignore: empty_catches
|
|
}
|
|
}
|
|
|
|
Future<void> _fetchTasks({bool showLoading = true}) async {
|
|
if (!mounted) return;
|
|
if (showLoading) setState(() => isLoading = true);
|
|
try {
|
|
final String? token = widget.userData['access_token'];
|
|
final response = await http.get(
|
|
Uri.parse('${AppConfig.baseUrl}/driver/tasks'),
|
|
headers: {
|
|
'Authorization': 'Bearer $token',
|
|
'Accept': 'application/json',
|
|
},
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
final data = json.decode(response.body);
|
|
if (mounted) {
|
|
setState(() {
|
|
tasks = data['data'] ?? [];
|
|
if (showLoading) isLoading = false;
|
|
});
|
|
}
|
|
} else {
|
|
if (mounted && showLoading) setState(() => isLoading = false);
|
|
}
|
|
} catch (e) {
|
|
if (mounted && showLoading) setState(() => isLoading = false);
|
|
}
|
|
}
|
|
|
|
void _showSnackBar(String msg, Color bg) {
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
|
content: Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
bg == const Color(0xFF10B981)
|
|
? Icons.check_circle_outline
|
|
: Icons.error_outline_rounded,
|
|
color: Colors.white,
|
|
size: 28,
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
bg == const Color(0xFF10B981) ? "Berhasil" : "Peringatan",
|
|
style: GoogleFonts.inter(
|
|
fontWeight: FontWeight.w800,
|
|
fontSize: 14,
|
|
color: Colors.white),
|
|
),
|
|
Text(
|
|
msg,
|
|
style: GoogleFonts.inter(
|
|
fontSize: 12,
|
|
color: Colors.white.withOpacity(0.9),
|
|
fontWeight: FontWeight.w500),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
behavior: SnackBarBehavior.floating,
|
|
backgroundColor: bg.withOpacity(0.95),
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
side: BorderSide(color: Colors.white.withOpacity(0.2), width: 1),
|
|
),
|
|
duration: const Duration(seconds: 3),
|
|
));
|
|
}
|
|
|
|
Future<void> _updateTaskStatus(Map<String, dynamic> task) async {
|
|
final String taskId = task['type_id'] ?? '';
|
|
if (_submittingTaskIds.contains(taskId)) return;
|
|
|
|
setState(() {
|
|
_submittingTaskIds.add(taskId);
|
|
});
|
|
|
|
final String? token = widget.userData['access_token'];
|
|
final id = task['type_id'].substring(1);
|
|
final isJemput = task['task_type'] == 'jemput';
|
|
final url = isJemput
|
|
? '${AppConfig.baseUrl}/pickups/$id/status'
|
|
: '${AppConfig.baseUrl}/orders/$id/update-status';
|
|
|
|
try {
|
|
final response = await http.put(
|
|
Uri.parse(url),
|
|
headers: {
|
|
'Authorization': 'Bearer $token',
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: json.encode({'status': isJemput ? 'SELESAI' : 'DITERIMA'}),
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
if (mounted) {
|
|
_showSnackBar(
|
|
"Tugas berhasil diselesaikan!", const Color(0xFF10B981));
|
|
_fetchTasks();
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// ignore: empty_catches
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() {
|
|
_submittingTaskIds.remove(taskId);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _openGoogleMaps(String address, String lat, String lng) async {
|
|
Uri navUri = (lat.isNotEmpty && lng.isNotEmpty && lat != '0.0')
|
|
? Uri.parse('google.navigation:q=$lat,$lng')
|
|
: Uri.parse('google.navigation:q=${Uri.encodeComponent(address)}');
|
|
|
|
if (await canLaunchUrl(navUri)) {
|
|
await launchUrl(navUri);
|
|
} else {
|
|
// Fallback jika tidak ada aplikasi Google Maps terinstall
|
|
Uri webUri = (lat.isNotEmpty && lng.isNotEmpty && lat != '0.0')
|
|
? Uri.parse(
|
|
'https://www.google.com/maps/dir/?api=1&destination=$lat,$lng')
|
|
: Uri.parse(
|
|
'https://www.google.com/maps/search/?api=1&query=${Uri.encodeComponent(address)}');
|
|
if (await canLaunchUrl(webUri)) {
|
|
await launchUrl(webUri, mode: LaunchMode.externalApplication);
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _sendWhatsApp(String phone, String customerName,
|
|
String orderNumber, String taskType, String taskId) async {
|
|
if (_sendingWhatsAppTaskIds.contains(taskId)) return;
|
|
|
|
setState(() {
|
|
_sendingWhatsAppTaskIds.add(taskId);
|
|
});
|
|
|
|
final String? token = widget.userData['access_token'];
|
|
try {
|
|
final response = await http.post(
|
|
Uri.parse('${AppConfig.baseUrl}/send-whatsapp'),
|
|
headers: {
|
|
'Authorization': 'Bearer $token',
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: json.encode({
|
|
'phone': phone,
|
|
'customer_name': customerName,
|
|
'task_type': taskType,
|
|
}),
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
if (mounted) {
|
|
_saveSentMessage(taskId);
|
|
_showSnackBar("Pesan WhatsApp terkirim", const Color(0xFF10B981));
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// ignore: empty_catches
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() {
|
|
_sendingWhatsAppTaskIds.remove(taskId);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
String userName = widget.userData['user']?['name'] ?? "Driver";
|
|
List<dynamic> pickupTasks =
|
|
tasks.where((t) => t['task_type'] == 'jemput').toList();
|
|
List<dynamic> deliveryTasks =
|
|
tasks.where((t) => t['task_type'] == 'antar').toList();
|
|
|
|
return SafeArea(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(20, 24, 20, 16),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text("Halo, $userName!",
|
|
style: GoogleFonts.inter(
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.w600,
|
|
color: onSurface,
|
|
letterSpacing: -0.5)),
|
|
const SizedBox(height: 4),
|
|
Text("Siap bertugas hari ini?",
|
|
style: GoogleFonts.inter(
|
|
fontSize: 13,
|
|
color: onSurfaceVariant,
|
|
fontWeight: FontWeight.w500)),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
child: Container(
|
|
height: 48,
|
|
padding: const EdgeInsets.all(6),
|
|
decoration: BoxDecoration(
|
|
color: surfaceContainerHigh,
|
|
borderRadius: BorderRadius.circular(999),
|
|
),
|
|
child: TabBar(
|
|
controller: _tabController,
|
|
indicator: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(999),
|
|
color: primary,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: primary.withOpacity(0.2),
|
|
blurRadius: 12,
|
|
offset: const Offset(0, 4))
|
|
],
|
|
),
|
|
labelColor: Colors.white,
|
|
unselectedLabelColor: onSurfaceVariant,
|
|
labelStyle: GoogleFonts.inter(
|
|
fontWeight: FontWeight.w500, fontSize: 13),
|
|
unselectedLabelStyle: GoogleFonts.inter(
|
|
fontWeight: FontWeight.w500, fontSize: 13),
|
|
indicatorSize: TabBarIndicatorSize.tab,
|
|
dividerColor: Colors.transparent,
|
|
splashBorderRadius: BorderRadius.circular(999),
|
|
tabs: [
|
|
Tab(text: "Pick-up (${pickupTasks.length})"),
|
|
Tab(text: "Delivery (${deliveryTasks.length})"),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Expanded(
|
|
child: isLoading
|
|
? const Center(child: CircularProgressIndicator())
|
|
: TabBarView(
|
|
controller: _tabController,
|
|
children: [
|
|
_buildTaskList(pickupTasks, "Tidak ada tugas pick up"),
|
|
_buildTaskList(deliveryTasks, "Tidak ada tugas antar"),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTaskList(List<dynamic> list, String emptyMessage) {
|
|
if (list.isEmpty) {
|
|
return RefreshIndicator(
|
|
onRefresh: _fetchTasks,
|
|
child: SingleChildScrollView(
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
child: Container(
|
|
height: MediaQuery.of(context).size.height * 0.5,
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(24),
|
|
alignment: Alignment.center,
|
|
child: Text(emptyMessage,
|
|
style: GoogleFonts.inter(fontSize: 13, color: outline)),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
return RefreshIndicator(
|
|
onRefresh: _fetchTasks,
|
|
child: ListView.separated(
|
|
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
|
|
itemCount: list.length,
|
|
separatorBuilder: (_, __) => const SizedBox(height: 12),
|
|
itemBuilder: (context, index) => _buildTaskCard(list[index]),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTaskCard(Map<String, dynamic> task) {
|
|
bool isJemput = task['task_type'] == 'jemput';
|
|
String customerName = task['customer_name'] ?? '-';
|
|
String address = task['address'] ?? '-';
|
|
String phone = task['phone'] ?? '';
|
|
String orderNumber = task['order_number'] ?? '';
|
|
if (!orderNumber.startsWith('#') && orderNumber.isNotEmpty) {
|
|
orderNumber = '#$orderNumber';
|
|
}
|
|
String lat = task['latitude']?.toString() ?? '';
|
|
String lng = task['longitude']?.toString() ?? '';
|
|
|
|
final payMethod = (task['payment_method'] ?? "").toString().toUpperCase();
|
|
final cashRec = num.tryParse(task['cash_received']?.toString() ?? "0") ?? 0;
|
|
final isUnpaid = payMethod == "BAYAR NANTI" && cashRec == 0;
|
|
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
color: surfaceContainerLowest,
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(color: outlineVariant.withOpacity(0.3)),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.04),
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
// Header (Order #, Name, Phone, Badges)
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(orderNumber,
|
|
style: GoogleFonts.inter(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.bold,
|
|
color: primary,
|
|
letterSpacing: -0.2)),
|
|
const SizedBox(height: 2),
|
|
Text(customerName,
|
|
style: GoogleFonts.inter(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.w600,
|
|
color: onSurface)),
|
|
if (phone.isNotEmpty) ...[
|
|
const SizedBox(height: 2),
|
|
Row(
|
|
children: [
|
|
Icon(Icons.call_rounded,
|
|
size: 14, color: outline),
|
|
const SizedBox(width: 6),
|
|
Text(phone,
|
|
style: GoogleFonts.inter(
|
|
fontSize: 11, color: outline)),
|
|
],
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
if (!isJemput) _buildPaymentBadge(task),
|
|
if (isJemput)
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 10, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: primary.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(999),
|
|
border:
|
|
Border.all(color: primary.withOpacity(0.2)),
|
|
),
|
|
child: Text(
|
|
"PICK-UP",
|
|
style: GoogleFonts.inter(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.bold,
|
|
color: primary,
|
|
),
|
|
),
|
|
),
|
|
if (!isJemput) ...[
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 8, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: secondaryContainer.withOpacity(0.5),
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.local_shipping_rounded,
|
|
size: 14, color: onSecondaryContainer),
|
|
const SizedBox(width: 4),
|
|
Text("Delivery",
|
|
style: GoogleFonts.inter(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.w500,
|
|
color: onSecondaryContainer)),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
|
|
// Nota for Delivery
|
|
if (!isJemput) ...[
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: surfaceContainerLow.withOpacity(0.5),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border:
|
|
Border.all(color: outlineVariant.withOpacity(0.1)),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
// Services
|
|
if (task['service_name'] != null &&
|
|
task['service_name'].toString().isNotEmpty) ...[
|
|
...task['service_name']
|
|
.toString()
|
|
.split(RegExp(r' \+ |\n'))
|
|
.map((item) {
|
|
List<String> parts = item.split('||');
|
|
String namePart = parts[0];
|
|
String pricePart = parts.length > 1 ? parts[1] : "";
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 8),
|
|
child: Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.dry_cleaning_rounded,
|
|
size: 18, color: primary),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: Text(namePart,
|
|
style: GoogleFonts.inter(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w500,
|
|
color: onSurface)),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (pricePart.isNotEmpty)
|
|
Text("Rp ${formatHarga(pricePart)}",
|
|
style: GoogleFonts.inter(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.bold,
|
|
color: primary)),
|
|
],
|
|
),
|
|
);
|
|
}),
|
|
],
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
padding: const EdgeInsets.only(top: 8),
|
|
decoration: BoxDecoration(
|
|
border: Border(
|
|
top: BorderSide(
|
|
color: outlineVariant.withOpacity(0.1))),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text("TOTAL TAGIHAN",
|
|
style: GoogleFonts.inter(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.bold,
|
|
color: outline,
|
|
letterSpacing: 1.0)),
|
|
Text("Rp ${formatHarga(task['total_price'])}",
|
|
style: GoogleFonts.inter(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.bold,
|
|
color: onSurface)),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
],
|
|
|
|
// Address
|
|
if (isJemput)
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: surfaceContainerLow.withOpacity(0.5),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border:
|
|
Border.all(color: outlineVariant.withOpacity(0.1)),
|
|
),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Icon(Icons.location_on_rounded,
|
|
size: 18, color: primary),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text("ALAMAT PICK UP",
|
|
style: GoogleFonts.inter(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.bold,
|
|
color: outline,
|
|
letterSpacing: 0.5)),
|
|
const SizedBox(height: 2),
|
|
Text(address,
|
|
style: GoogleFonts.inter(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w500,
|
|
color: onSurface)),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
else
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Icon(Icons.location_on_rounded,
|
|
size: 16, color: outline),
|
|
const SizedBox(width: 6),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text("ALAMAT DELIVERY",
|
|
style: GoogleFonts.inter(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.bold,
|
|
color: outline,
|
|
letterSpacing: 0.5)),
|
|
const SizedBox(height: 2),
|
|
Text(address,
|
|
style: GoogleFonts.inter(
|
|
fontSize: 11,
|
|
color: onSurface,
|
|
height: 1.2)),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Buttons Map & WA
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: InkWell(
|
|
onTap: () => _openGoogleMaps(address, lat, lng),
|
|
borderRadius: BorderRadius.circular(12),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
|
decoration: BoxDecoration(
|
|
color: surfaceContainerHigh,
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(Icons.map_rounded,
|
|
size: 18, color: onSurfaceVariant),
|
|
const SizedBox(width: 8),
|
|
Text("Rute",
|
|
style: GoogleFonts.inter(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w500,
|
|
color: onSurfaceVariant)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: InkWell(
|
|
onTap: (_sentMessages.contains(task['type_id']) ||
|
|
_sendingWhatsAppTaskIds.contains(task['type_id']))
|
|
? null
|
|
: () => _sendWhatsApp(phone, customerName,
|
|
orderNumber, task['task_type'], task['type_id']),
|
|
borderRadius: BorderRadius.circular(12),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
|
decoration: BoxDecoration(
|
|
color: _sentMessages.contains(task['type_id'])
|
|
? surfaceContainerHigh
|
|
: (_sendingWhatsAppTaskIds.contains(task['type_id'])
|
|
? secondaryContainer.withOpacity(0.25)
|
|
: secondaryContainer.withOpacity(0.5)),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
if (_sendingWhatsAppTaskIds.contains(task['type_id'])) ...[
|
|
SizedBox(
|
|
height: 14,
|
|
width: 14,
|
|
child: CircularProgressIndicator(
|
|
color: onSecondaryContainer,
|
|
strokeWidth: 2,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
"Mengirim...",
|
|
style: GoogleFonts.inter(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w500,
|
|
color: onSecondaryContainer,
|
|
),
|
|
),
|
|
] else ...[
|
|
Icon(
|
|
_sentMessages.contains(task['type_id'])
|
|
? Icons.check_circle_rounded
|
|
: Icons.chat_rounded,
|
|
size: 18,
|
|
color: _sentMessages.contains(task['type_id'])
|
|
? outline
|
|
: onSecondaryContainer,
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
_sentMessages.contains(task['type_id'])
|
|
? "Terkirim"
|
|
: "Konfirmasi",
|
|
style: GoogleFonts.inter(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w500,
|
|
color: _sentMessages.contains(task['type_id'])
|
|
? outline
|
|
: onSecondaryContainer,
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
if (isJemput) ...[
|
|
const SizedBox(height: 12),
|
|
InkWell(
|
|
onTap: _submittingTaskIds.contains(task['type_id'])
|
|
? null
|
|
: () => _updateTaskStatus(task),
|
|
borderRadius: BorderRadius.circular(12),
|
|
child: Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
decoration: BoxDecoration(
|
|
color: _submittingTaskIds.contains(task['type_id'])
|
|
? primary.withOpacity(0.6)
|
|
: primary,
|
|
borderRadius: BorderRadius.circular(12),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 2,
|
|
offset: const Offset(0, 1))
|
|
],
|
|
),
|
|
child: Center(
|
|
child: _submittingTaskIds.contains(task['type_id'])
|
|
? const SizedBox(
|
|
height: 16,
|
|
width: 16,
|
|
child: CircularProgressIndicator(
|
|
color: Colors.white,
|
|
strokeWidth: 2,
|
|
),
|
|
)
|
|
: Text("Finish Pick-up",
|
|
style: GoogleFonts.inter(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w600,
|
|
color: onPrimary)),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
if (!isJemput)
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
decoration: BoxDecoration(
|
|
color: surfaceContainerLowest,
|
|
border: Border(
|
|
top: BorderSide(color: outlineVariant.withOpacity(0.1))),
|
|
borderRadius:
|
|
const BorderRadius.vertical(bottom: Radius.circular(16)),
|
|
),
|
|
child: InkWell(
|
|
onTap: _submittingTaskIds.contains(task['type_id'])
|
|
? null
|
|
: () {
|
|
if (isUnpaid) {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => HomePaymentPage(
|
|
order: task,
|
|
userData: widget.userData,
|
|
),
|
|
),
|
|
).then((_) => _fetchTasks());
|
|
} else {
|
|
_updateTaskStatus(task);
|
|
}
|
|
},
|
|
borderRadius: BorderRadius.circular(12),
|
|
child: Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
|
decoration: BoxDecoration(
|
|
color: _submittingTaskIds.contains(task['type_id'])
|
|
? primary.withOpacity(0.6)
|
|
: primary,
|
|
borderRadius: BorderRadius.circular(12),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 2,
|
|
offset: const Offset(0, 1))
|
|
],
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: _submittingTaskIds.contains(task['type_id'])
|
|
? [
|
|
const SizedBox(
|
|
height: 16,
|
|
width: 16,
|
|
child: CircularProgressIndicator(
|
|
color: Colors.white,
|
|
strokeWidth: 2,
|
|
),
|
|
)
|
|
]
|
|
: [
|
|
Text("Finish Delivery",
|
|
style: GoogleFonts.inter(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
color: onPrimary)),
|
|
const SizedBox(width: 8),
|
|
Icon(Icons.check_circle_rounded,
|
|
size: 18, color: onPrimary),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|