697 lines
22 KiB
Dart
697 lines
22 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'dart:convert';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import '../../../services/auth_service.dart';
|
|
import 'laporan_user_screen.dart';
|
|
|
|
class JadwalUserScreen extends StatefulWidget {
|
|
const JadwalUserScreen({super.key});
|
|
|
|
@override
|
|
State<JadwalUserScreen> createState() => _JadwalUserScreenState();
|
|
}
|
|
|
|
class _JadwalUserScreenState extends State<JadwalUserScreen> {
|
|
List<Map<String, dynamic>> jadwalList = [];
|
|
bool isLoading = true;
|
|
String namaUser = '';
|
|
|
|
int selectedMonth = DateTime.now().month;
|
|
int selectedYear = DateTime.now().year;
|
|
|
|
List<int> years = [DateTime.now().year];
|
|
|
|
Future<void> getUserLocal() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
setState(() {
|
|
namaUser = prefs.getString('nama_user') ?? '';
|
|
});
|
|
}
|
|
|
|
void generateYears() {
|
|
Set<int> yearSet = {DateTime.now().year};
|
|
for (var jadwal in jadwalList) {
|
|
DateTime date = DateTime.parse(jadwal['tanggal']);
|
|
yearSet.add(date.year);
|
|
}
|
|
List<int> result = yearSet.toList()..sort();
|
|
setState(() {
|
|
years = result;
|
|
if (!years.contains(selectedYear)) {
|
|
selectedYear = DateTime.now().year;
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> fetchJadwal() async {
|
|
String? token = await AuthService.getToken();
|
|
|
|
if (token == null) {
|
|
setState(() => isLoading = false);
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Token tidak ditemukan, silakan login ulang')),
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
|
|
try {
|
|
final response = await http.get(
|
|
Uri.parse("${AuthService.baseUrl}/jadwal"),
|
|
headers: {
|
|
"Accept": "application/json",
|
|
"Authorization": "Bearer $token",
|
|
},
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
|
|
List<Map<String, dynamic>> temp =
|
|
List<Map<String, dynamic>>.from(data['jadwal']);
|
|
|
|
// Sort awal dari server: urut tanggal ascending
|
|
temp.sort((a, b) => DateTime.parse(a['tanggal'])
|
|
.compareTo(DateTime.parse(b['tanggal'])));
|
|
|
|
setState(() {
|
|
jadwalList = temp;
|
|
isLoading = false;
|
|
});
|
|
|
|
generateYears();
|
|
} else {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(
|
|
'Error ${response.statusCode}: ${response.body}')),
|
|
);
|
|
}
|
|
setState(() => isLoading = false);
|
|
}
|
|
} catch (e) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Error: $e')),
|
|
);
|
|
}
|
|
setState(() => isLoading = false);
|
|
}
|
|
}
|
|
|
|
// ── Filter + Sort: hari ini di atas, lewat di bawah ──
|
|
List<Map<String, dynamic>> get filteredJadwal {
|
|
if (jadwalList.isEmpty) return [];
|
|
|
|
final now = DateTime.now();
|
|
final today = DateTime(now.year, now.month, now.day);
|
|
|
|
final filtered = jadwalList.where((jadwal) {
|
|
final date = DateTime.parse(jadwal['tanggal']);
|
|
return date.month == selectedMonth && date.year == selectedYear;
|
|
}).toList();
|
|
|
|
// Prioritas urutan tampilan:
|
|
// 0 = hari ini (paling atas)
|
|
// 1 = akan datang
|
|
// 2 = sudah lewat (paling bawah)
|
|
int priority(Map<String, dynamic> jadwal) {
|
|
final tanggal = DateTime.parse(jadwal['tanggal']);
|
|
if (tanggal.year == today.year &&
|
|
tanggal.month == today.month &&
|
|
tanggal.day == today.day) return 0;
|
|
if (tanggal.isAfter(today)) return 1;
|
|
return 2;
|
|
}
|
|
|
|
filtered.sort((a, b) {
|
|
final pa = priority(a);
|
|
final pb = priority(b);
|
|
if (pa != pb) return pa.compareTo(pb);
|
|
// Dalam grup yang sama, urutkan tanggal ascending
|
|
return DateTime.parse(a['tanggal'])
|
|
.compareTo(DateTime.parse(b['tanggal']));
|
|
});
|
|
|
|
return filtered;
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
loadData();
|
|
}
|
|
|
|
Future<void> loadData() async {
|
|
setState(() => isLoading = true);
|
|
await getUserLocal();
|
|
await fetchJadwal();
|
|
}
|
|
|
|
Color getStatusColor(String status) {
|
|
switch (status.toLowerCase()) {
|
|
case 'selesai':
|
|
return const Color(0xFF4CAF50);
|
|
case 'sedang':
|
|
return const Color(0xFF2196F3);
|
|
case 'libur':
|
|
return Colors.redAccent;
|
|
default:
|
|
return const Color(0xFFFF9800);
|
|
}
|
|
}
|
|
|
|
String getStatusText(String status) {
|
|
switch (status.toLowerCase()) {
|
|
case 'sedang':
|
|
return "Sedang Patroli";
|
|
case 'selesai':
|
|
return "Selesai Patroli";
|
|
case 'libur':
|
|
return "Libur";
|
|
default:
|
|
return "Belum Patroli";
|
|
}
|
|
}
|
|
|
|
// bool _dalamJamPatroli(String? jamMulaiStr, String? jamSelesaiStr) {
|
|
// if (jamMulaiStr == null || jamSelesaiStr == null) return false;
|
|
// final now = DateTime.now();
|
|
// final mulaiParts = jamMulaiStr.split(':');
|
|
// final selesaiParts = jamSelesaiStr.split(':');
|
|
// final jamMulai = DateTime(now.year, now.month, now.day,
|
|
// int.parse(mulaiParts[0]), int.parse(mulaiParts[1]));
|
|
// final jamSelesai = DateTime(now.year, now.month, now.day,
|
|
// int.parse(selesaiParts[0]), int.parse(selesaiParts[1]));
|
|
// return now.isAfter(jamMulai) && now.isBefore(jamSelesai);
|
|
// }
|
|
|
|
// bool _belumWaktuPatroli(String? jamMulaiStr) {
|
|
// if (jamMulaiStr == null) return false;
|
|
// final now = DateTime.now();
|
|
// final mulaiParts = jamMulaiStr.split(':');
|
|
// final jamMulai = DateTime(now.year, now.month, now.day,
|
|
// int.parse(mulaiParts[0]), int.parse(mulaiParts[1]));
|
|
// return now.isBefore(jamMulai);
|
|
// }
|
|
|
|
// bool _sudahLewatJamPatroli(String? jamSelesaiStr) {
|
|
// if (jamSelesaiStr == null) return false;
|
|
// final now = DateTime.now();
|
|
// final selesaiParts = jamSelesaiStr.split(':');
|
|
// final jamSelesai = DateTime(now.year, now.month, now.day,
|
|
// int.parse(selesaiParts[0]), int.parse(selesaiParts[1]));
|
|
// return now.isAfter(jamSelesai);
|
|
// }
|
|
|
|
List<DateTime>? _rangePatroli(String? jamMulaiStr, String? jamSelesaiStr) {
|
|
if (jamMulaiStr == null || jamSelesaiStr == null) return null;
|
|
final now = DateTime.now();
|
|
final mulaiParts = jamMulaiStr.split(':');
|
|
final selesaiParts = jamSelesaiStr.split(':');
|
|
|
|
DateTime jamMulai = DateTime(now.year, now.month, now.day,
|
|
int.parse(mulaiParts[0]), int.parse(mulaiParts[1]));
|
|
DateTime jamSelesai = DateTime(now.year, now.month, now.day,
|
|
int.parse(selesaiParts[0]), int.parse(selesaiParts[1]));
|
|
|
|
if (!jamSelesai.isAfter(jamMulai)) {
|
|
if (now.isBefore(jamSelesai)) {
|
|
jamMulai = jamMulai.subtract(const Duration(days: 1));
|
|
} else {
|
|
jamSelesai = jamSelesai.add(const Duration(days: 1));
|
|
}
|
|
}
|
|
|
|
return [jamMulai, jamSelesai];
|
|
}
|
|
|
|
bool _dalamJamPatroli(String? jamMulaiStr, String? jamSelesaiStr) {
|
|
final range = _rangePatroli(jamMulaiStr, jamSelesaiStr);
|
|
if (range == null) return false;
|
|
final now = DateTime.now();
|
|
return now.isAfter(range[0]) && now.isBefore(range[1]);
|
|
}
|
|
|
|
bool _belumWaktuPatroli(String? jamMulaiStr, String? jamSelesaiStr) {
|
|
final range = _rangePatroli(jamMulaiStr, jamSelesaiStr);
|
|
if (range == null) return false;
|
|
return DateTime.now().isBefore(range[0]);
|
|
}
|
|
|
|
bool _sudahLewatJamPatroli(String? jamMulaiStr, String? jamSelesaiStr) {
|
|
final range = _rangePatroli(jamMulaiStr, jamSelesaiStr);
|
|
if (range == null) return false;
|
|
return DateTime.now().isAfter(range[1]);
|
|
}
|
|
|
|
Widget _buildFilter() {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
|
color: Colors.white,
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: DropdownButtonFormField<int>(
|
|
value: selectedMonth,
|
|
decoration: const InputDecoration(
|
|
labelText: "Bulan",
|
|
border: OutlineInputBorder(),
|
|
isDense: true,
|
|
),
|
|
items: const [
|
|
DropdownMenuItem(value: 1, child: Text("Januari")),
|
|
DropdownMenuItem(value: 2, child: Text("Februari")),
|
|
DropdownMenuItem(value: 3, child: Text("Maret")),
|
|
DropdownMenuItem(value: 4, child: Text("April")),
|
|
DropdownMenuItem(value: 5, child: Text("Mei")),
|
|
DropdownMenuItem(value: 6, child: Text("Juni")),
|
|
DropdownMenuItem(value: 7, child: Text("Juli")),
|
|
DropdownMenuItem(value: 8, child: Text("Agustus")),
|
|
DropdownMenuItem(value: 9, child: Text("September")),
|
|
DropdownMenuItem(value: 10, child: Text("Oktober")),
|
|
DropdownMenuItem(value: 11, child: Text("November")),
|
|
DropdownMenuItem(value: 12, child: Text("Desember")),
|
|
],
|
|
onChanged: (value) => setState(() => selectedMonth = value!),
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: DropdownButtonFormField<int>(
|
|
value: years.contains(selectedYear) ? selectedYear : years.first,
|
|
decoration: const InputDecoration(
|
|
labelText: "Tahun",
|
|
border: OutlineInputBorder(),
|
|
isDense: true,
|
|
),
|
|
items: years
|
|
.map((year) => DropdownMenuItem(
|
|
value: year,
|
|
child: Text(year.toString()),
|
|
))
|
|
.toList(),
|
|
onChanged: (value) => setState(() => selectedYear = value!),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: const Color(0xFFF5F7FB),
|
|
appBar: AppBar(
|
|
toolbarHeight: 70,
|
|
title: const Text(
|
|
'Jadwal Patroli',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
fontSize: 18,
|
|
),
|
|
),
|
|
centerTitle: true,
|
|
backgroundColor: const Color(0xFF2F5BEA),
|
|
elevation: 0,
|
|
iconTheme: const IconThemeData(color: Colors.white),
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(bottom: Radius.circular(20)),
|
|
),
|
|
),
|
|
body: RefreshIndicator(
|
|
onRefresh: loadData,
|
|
child: isLoading
|
|
? const Center(
|
|
child:
|
|
CircularProgressIndicator(color: Color(0xFF2F5BEA)),
|
|
)
|
|
: Column(
|
|
children: [
|
|
_buildFilter(),
|
|
Expanded(child: _buildContent()),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildContent() {
|
|
if (filteredJadwal.isEmpty) {
|
|
return ListView(
|
|
children: [
|
|
SizedBox(height: MediaQuery.of(context).size.height * 0.3),
|
|
const Center(
|
|
child: Column(
|
|
children: [
|
|
Icon(Icons.event_busy, size: 80, color: Colors.grey),
|
|
SizedBox(height: 16),
|
|
Text(
|
|
"Tidak ada jadwal untuk bulan ini",
|
|
style: TextStyle(color: Colors.grey, fontSize: 16),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
return ListView.builder(
|
|
padding: const EdgeInsets.fromLTRB(20, 10, 20, 20),
|
|
itemCount: filteredJadwal.length,
|
|
itemBuilder: (context, index) {
|
|
final jadwal = filteredJadwal[index];
|
|
|
|
final now = DateTime.now();
|
|
final today = DateTime(now.year, now.month, now.day);
|
|
final tomorrow = today.add(const Duration(days: 1));
|
|
final tanggalJadwal = DateTime.parse(jadwal['tanggal']);
|
|
|
|
final bool isToday = tanggalJadwal.year == today.year &&
|
|
tanggalJadwal.month == today.month &&
|
|
tanggalJadwal.day == today.day;
|
|
final bool isBesok = tanggalJadwal.year == tomorrow.year &&
|
|
tanggalJadwal.month == tomorrow.month &&
|
|
tanggalJadwal.day == tomorrow.day;
|
|
final bool isLewat = tanggalJadwal.isBefore(today);
|
|
final bool isFuture = tanggalJadwal.isAfter(today);
|
|
|
|
final status = jadwal['status'] ?? 'belum';
|
|
final isLibur = status.toLowerCase() == 'libur';
|
|
final laporanCount = jadwal['laporan_count'] ?? 0;
|
|
final minimumTerpenuhi = jadwal['minimum_terpenuhi'] == true;
|
|
final isSelesai = minimumTerpenuhi || status.toLowerCase() == 'selesai';
|
|
|
|
final namaLokasi =
|
|
isLibur ? 'Hari Libur' : (jadwal['nama_lokasi'] ?? '-');
|
|
final jamMulaiStr = jadwal['jam_mulai'] as String?;
|
|
final jamSelesaiStr = jadwal['jam_selesai'] as String?;
|
|
final jamDisplay = (jamMulaiStr != null && jamSelesaiStr != null)
|
|
? "$jamMulaiStr - $jamSelesaiStr"
|
|
: null;
|
|
final namaShift = jadwal['nama_shift'] as String?;
|
|
|
|
bool isDisabled = true;
|
|
if (isToday && !isLibur) {
|
|
isDisabled = !_dalamJamPatroli(jamMulaiStr, jamSelesaiStr);
|
|
}
|
|
|
|
String infoText;
|
|
Color infoColor;
|
|
|
|
if (isLibur) {
|
|
infoText = "Hari istirahat, tidak ada patroli";
|
|
infoColor = Colors.redAccent;
|
|
} else if (isSelesai) {
|
|
infoText = "Minimum patroli terpenuhi";
|
|
infoColor = Colors.green;
|
|
} else if (isToday) {
|
|
if (_belumWaktuPatroli(jamMulaiStr, jamSelesaiStr)) {
|
|
infoText = "Belum waktunya patroli (mulai $jamMulaiStr)";
|
|
infoColor = Colors.orange;
|
|
} else if (_sudahLewatJamPatroli(jamMulaiStr, jamSelesaiStr)) {
|
|
infoText = "Waktu patroli sudah habis";
|
|
infoColor = Colors.grey;
|
|
} else {
|
|
infoText = "Klik untuk patroli";
|
|
infoColor = const Color(0xFF2F5BEA);
|
|
}
|
|
} else if (isLewat) {
|
|
infoText = "Patroli terlewat";
|
|
infoColor = Colors.grey;
|
|
} else if (isFuture) {
|
|
infoText = "Belum waktunya patroli";
|
|
infoColor = Colors.orange;
|
|
} else {
|
|
infoText = "";
|
|
infoColor = Colors.grey;
|
|
}
|
|
|
|
return _JadwalCard(
|
|
lokasi: namaLokasi,
|
|
jam: jamDisplay,
|
|
tanggal: jadwal['tanggal'] ?? '-',
|
|
status: getStatusText(status),
|
|
laporanCount: laporanCount,
|
|
minimumTerpenuhi: minimumTerpenuhi,
|
|
statusColor: getStatusColor(status),
|
|
isLewat: isLewat,
|
|
isToday: isToday,
|
|
isBesok: isBesok,
|
|
isFuture: isFuture,
|
|
isLibur: isLibur,
|
|
infoText: infoText,
|
|
infoColor: infoColor,
|
|
onTap: isDisabled
|
|
? null
|
|
: () async {
|
|
await Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => LaporanUserScreen(
|
|
lokasi: jadwal['nama_lokasi'] ?? '-',
|
|
tanggal: jadwal['tanggal'] ?? '-',
|
|
shift: namaShift ?? '-',
|
|
jadwalId: jadwal['id'].toString(),
|
|
namaPetugas:
|
|
namaUser.isEmpty ? '-' : namaUser,
|
|
),
|
|
),
|
|
);
|
|
fetchJadwal();
|
|
},
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
// ════════════════════════════════════════════════════════════════════════════
|
|
// CARD WIDGET
|
|
// ════════════════════════════════════════════════════════════════════════════
|
|
|
|
class _JadwalCard extends StatelessWidget {
|
|
final String lokasi;
|
|
final String? jam;
|
|
final String tanggal;
|
|
final String status;
|
|
final int laporanCount;
|
|
final bool minimumTerpenuhi;
|
|
final Color statusColor;
|
|
final bool isLewat;
|
|
final bool isToday;
|
|
final bool isBesok;
|
|
final bool isFuture;
|
|
final bool isLibur;
|
|
final String infoText;
|
|
final Color infoColor;
|
|
final VoidCallback? onTap;
|
|
|
|
const _JadwalCard({
|
|
required this.lokasi,
|
|
required this.jam,
|
|
required this.tanggal,
|
|
required this.status,
|
|
required this.laporanCount,
|
|
required this.minimumTerpenuhi,
|
|
required this.statusColor,
|
|
required this.isLewat,
|
|
required this.isToday,
|
|
required this.isBesok,
|
|
required this.isFuture,
|
|
required this.isLibur,
|
|
required this.infoText,
|
|
required this.infoColor,
|
|
this.onTap,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
Color cardColor = Colors.white;
|
|
if (isLibur) {
|
|
cardColor = Colors.red.shade50;
|
|
} else if (isLewat) {
|
|
cardColor = Colors.grey.shade200;
|
|
} else if (isToday) {
|
|
cardColor = const Color(0xFFE8F0FF);
|
|
}
|
|
|
|
return Container(
|
|
margin: const EdgeInsets.only(bottom: 16),
|
|
decoration: BoxDecoration(
|
|
color: cardColor,
|
|
borderRadius: BorderRadius.circular(15),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
onTap: onTap,
|
|
borderRadius: BorderRadius.circular(15),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// ── HEADER: Lokasi + Badge Status ──
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
lokasi,
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: isLibur
|
|
? Colors.redAccent
|
|
: const Color(0xFF2D3243),
|
|
),
|
|
),
|
|
),
|
|
if (isLibur)
|
|
_badge("Libur", Colors.redAccent)
|
|
else
|
|
_buildStatusBadge(),
|
|
],
|
|
),
|
|
|
|
if (isToday && !isLibur)
|
|
_badge("Hari Ini", const Color(0xFF2F5BEA)),
|
|
if (isBesok && !isLibur)
|
|
_badge("Besok", Colors.orange),
|
|
|
|
const Divider(height: 24),
|
|
|
|
// ── INFO: Jam & Tanggal ──
|
|
if (isLibur)
|
|
_buildInfoItem(Icons.calendar_today_rounded, tanggal)
|
|
else
|
|
Row(
|
|
children: [
|
|
if (jam != null) ...[
|
|
_buildInfoItem(Icons.access_time_rounded, jam!),
|
|
const SizedBox(width: 20),
|
|
],
|
|
_buildInfoItem(
|
|
Icons.calendar_today_rounded, tanggal),
|
|
],
|
|
),
|
|
|
|
// ── PROGRESS ──
|
|
if (!isLibur) ...[
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
children: [
|
|
const Text(
|
|
"Progress Patroli : ",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
Text(
|
|
"$laporanCount / 3 minimum",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: minimumTerpenuhi
|
|
? Colors.green
|
|
: const Color(0xFF2F5BEA),
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
|
|
const SizedBox(height: 10),
|
|
|
|
// ── ACTION TEXT ──
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
Flexible(
|
|
child: Text(
|
|
infoText,
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: infoColor,
|
|
),
|
|
textAlign: TextAlign.end,
|
|
),
|
|
),
|
|
if (onTap != null)
|
|
const Icon(
|
|
Icons.chevron_right,
|
|
size: 16,
|
|
color: Color(0xFF2F5BEA),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _badge(String text, Color color) {
|
|
return Container(
|
|
margin: const EdgeInsets.only(top: 6),
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
|
decoration: BoxDecoration(
|
|
color: color,
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
child: Text(
|
|
text,
|
|
style: const TextStyle(color: Colors.white, fontSize: 10),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatusBadge() {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: statusColor.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(color: statusColor.withOpacity(0.5)),
|
|
),
|
|
child: Text(
|
|
status,
|
|
style: TextStyle(
|
|
color: statusColor,
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w800,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildInfoItem(IconData icon, String text) {
|
|
return Row(
|
|
children: [
|
|
Icon(icon, size: 16, color: Colors.grey[600]),
|
|
const SizedBox(width: 6),
|
|
Text(text,
|
|
style: TextStyle(color: Colors.grey[700], fontSize: 13)),
|
|
],
|
|
);
|
|
}
|
|
} |