844 lines
37 KiB
Dart
844 lines
37 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../services/crm_api_service.dart';
|
|
import '../theme/app_theme.dart';
|
|
import '../utils/snackbar_utils.dart';
|
|
|
|
class SurveysListScreen extends StatefulWidget {
|
|
final List<dynamic> surveys;
|
|
final VoidCallback onDataChanged;
|
|
final bool isActive;
|
|
|
|
const SurveysListScreen({
|
|
super.key,
|
|
required this.surveys,
|
|
required this.onDataChanged,
|
|
this.isActive = false,
|
|
});
|
|
|
|
@override
|
|
State<SurveysListScreen> createState() => _SurveysListScreenState();
|
|
}
|
|
|
|
class _SurveysListScreenState extends State<SurveysListScreen> {
|
|
final CrmApiService _api = CrmApiService();
|
|
bool _isSaving = false;
|
|
|
|
void _deleteSurvey(String id) async {
|
|
final confirm = await showDialog<bool>(
|
|
context: context,
|
|
builder: (ctx) => Dialog(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(color: AppColors.primary.withValues(alpha: 0.1), shape: BoxShape.circle),
|
|
child: const Icon(Icons.warning_amber_rounded, color: AppColors.primary, size: 32),
|
|
),
|
|
const SizedBox(height: 16),
|
|
const Text('Delete Survey?', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w800, color: AppColors.darkBlue)),
|
|
const SizedBox(height: 8),
|
|
const Text('This action cannot be undone. Are you sure you want to delete this record?', textAlign: TextAlign.center, style: TextStyle(fontSize: 14, color: AppColors.textSecondary)),
|
|
const SizedBox(height: 24),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: TextButton(
|
|
onPressed: () => Navigator.pop(ctx, false),
|
|
style: TextButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))),
|
|
child: const Text('Cancel', style: TextStyle(color: AppColors.textSecondary, fontWeight: FontWeight.w700)),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: ElevatedButton(
|
|
onPressed: () => Navigator.pop(ctx, true),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppColors.primary,
|
|
elevation: 0,
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
),
|
|
child: const Text('Delete', style: TextStyle(color: Colors.white, fontWeight: FontWeight.w700)),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
if (confirm != true) return;
|
|
|
|
setState(() => _isSaving = true);
|
|
final success = await _api.deleteSurvey(id);
|
|
setState(() => _isSaving = false);
|
|
|
|
if (success) {
|
|
widget.onDataChanged();
|
|
if (mounted) {
|
|
SnackbarUtils.showSuccess(context, 'Survey deleted successfully.');
|
|
Navigator.pop(context); // Optional: close screen or refresh
|
|
}
|
|
} else {
|
|
if (mounted) {
|
|
SnackbarUtils.showError(context, 'Failed to delete survey.');
|
|
}
|
|
}
|
|
}
|
|
|
|
void _showEditSurveyDialog(Map<String, dynamic> survey) {
|
|
_showSurveyFormDialog(survey: survey);
|
|
}
|
|
|
|
void _showAddSurveyDialog() {
|
|
_showSurveyFormDialog();
|
|
}
|
|
|
|
void _showSurveyFormDialog({Map<String, dynamic>? survey}) {
|
|
final isEditing = survey != null;
|
|
final companyCtrl = TextEditingController(text: survey?['company']?.toString() ?? '');
|
|
final areaCtrl = TextEditingController(text: survey?['area']?.toString() ?? '');
|
|
final phoneCtrl = TextEditingController(text: survey?['phone']?.toString() ?? '');
|
|
final rateCtrl = TextEditingController(text: survey?['rate_per_kg']?.toString() ?? '');
|
|
final addressCtrl = TextEditingController(text: survey?['address']?.toString() ?? '');
|
|
final id = survey?['id']?.toString() ?? '';
|
|
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (ctx) {
|
|
return Dialog(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// Header
|
|
Container(
|
|
padding: const EdgeInsets.fromLTRB(24, 24, 24, 20),
|
|
decoration: const BoxDecoration(
|
|
color: AppColors.blueLight,
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(10),
|
|
decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle),
|
|
child: Icon(isEditing ? Icons.edit_outlined : Icons.add_circle_outline, color: AppColors.primary, size: 24),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(isEditing ? 'Edit Survey' : 'Add Competitor Survey', style: const TextStyle(fontWeight: FontWeight.w800, color: AppColors.darkBlue, fontSize: 18)),
|
|
const SizedBox(height: 2),
|
|
Text(isEditing ? 'Update competitor details' : 'Log a new provider survey', style: const TextStyle(fontSize: 12, color: AppColors.textSecondary)),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
// Form Fields
|
|
Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
children: [
|
|
_buildModernTextField(
|
|
controller: companyCtrl,
|
|
label: 'Company / Provider',
|
|
hint: 'e.g. DTDC, Delhivery',
|
|
icon: Icons.storefront_outlined,
|
|
),
|
|
const SizedBox(height: 16),
|
|
_buildModernTextField(
|
|
controller: areaCtrl,
|
|
label: 'Area / Zone',
|
|
hint: 'e.g. SOUTH ZONE',
|
|
icon: Icons.place_outlined,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildModernTextField(
|
|
controller: phoneCtrl,
|
|
label: 'Contact Number',
|
|
hint: 'Optional',
|
|
icon: Icons.phone_outlined,
|
|
keyboardType: TextInputType.phone,
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: _buildModernTextField(
|
|
controller: rateCtrl,
|
|
label: 'Rate / KG',
|
|
hint: '0.00',
|
|
icon: Icons.currency_rupee,
|
|
keyboardType: TextInputType.number,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
_buildModernTextField(
|
|
controller: addressCtrl,
|
|
label: 'Full Address',
|
|
hint: 'Optional physical address',
|
|
icon: Icons.map_outlined,
|
|
maxLines: 2,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
// Actions
|
|
Container(
|
|
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: TextButton(
|
|
onPressed: () => Navigator.pop(ctx),
|
|
style: TextButton.styleFrom(
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
),
|
|
child: const Text('Cancel', style: TextStyle(color: AppColors.textSecondary, fontWeight: FontWeight.w700)),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: ElevatedButton(
|
|
onPressed: () async {
|
|
if (companyCtrl.text.isEmpty || areaCtrl.text.isEmpty) {
|
|
SnackbarUtils.showError(ctx, 'Company and Area are required');
|
|
return;
|
|
}
|
|
|
|
setState(() => _isSaving = true);
|
|
Navigator.pop(ctx);
|
|
|
|
final payload = {
|
|
'company': companyCtrl.text,
|
|
'area': areaCtrl.text,
|
|
'phone': phoneCtrl.text,
|
|
'rate_per_kg': rateCtrl.text,
|
|
'address': addressCtrl.text,
|
|
'offers_pickup': survey?['offers_pickup'] ?? 'no',
|
|
'offers_drop': survey?['offers_drop'] ?? 'no',
|
|
'packing_charge': survey?['packing_charge'] ?? '',
|
|
'time_in_days': survey?['time_in_days'] ?? '',
|
|
'plus_code': survey?['plus_code'] ?? '',
|
|
'frequency': survey?['frequency'] ?? 'Daily',
|
|
'pincodes': survey?['pincodes'] ?? '',
|
|
};
|
|
|
|
final success = isEditing
|
|
? await _api.updateSurvey(id, payload)
|
|
: await _api.createSurvey(payload);
|
|
|
|
setState(() => _isSaving = false);
|
|
|
|
if (success) {
|
|
widget.onDataChanged();
|
|
if (mounted) {
|
|
SnackbarUtils.showSuccess(context, isEditing ? 'Survey updated!' : 'Survey added!');
|
|
}
|
|
} else {
|
|
if (mounted) {
|
|
SnackbarUtils.showError(context, isEditing ? 'Update failed.' : 'Failed to add survey.');
|
|
}
|
|
}
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppColors.primary,
|
|
elevation: 0,
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
),
|
|
child: Text(isEditing ? 'Save Changes' : 'Add Survey', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700)),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildModernTextField({
|
|
required TextEditingController controller,
|
|
required String label,
|
|
required String hint,
|
|
required IconData icon,
|
|
TextInputType keyboardType = TextInputType.text,
|
|
int maxLines = 1,
|
|
}) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
label,
|
|
style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w800, color: AppColors.darkBlue, letterSpacing: 0.5),
|
|
),
|
|
const SizedBox(height: 8),
|
|
TextField(
|
|
controller: controller,
|
|
keyboardType: keyboardType,
|
|
maxLines: maxLines,
|
|
style: const TextStyle(fontWeight: FontWeight.w600, color: AppColors.darkBlue, fontSize: 14),
|
|
decoration: InputDecoration(
|
|
hintText: hint,
|
|
hintStyle: TextStyle(color: AppColors.textSecondary.withValues(alpha: 0.5)),
|
|
prefixIcon: maxLines == 1 ? Icon(icon, size: 18, color: AppColors.textSecondary) : null,
|
|
filled: true,
|
|
fillColor: const Color(0xFFF8F9FA),
|
|
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: maxLines == 1 ? 16 : 12),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide(color: Colors.grey.shade200),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: const BorderSide(color: AppColors.primary),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildStatCard(String label, String value, IconData icon) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: Colors.grey.shade200),
|
|
boxShadow: [
|
|
BoxShadow(color: Colors.black.withValues(alpha: 0.02), blurRadius: 4, offset: const Offset(0, 2)),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Icon(icon, size: 16, color: AppColors.primary),
|
|
const SizedBox(height: 8),
|
|
Text(value, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w800, color: AppColors.darkBlue)),
|
|
const SizedBox(height: 2),
|
|
Text(label, style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: AppColors.textSecondary)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// Group surveys by company
|
|
Map<String, List<dynamic>> get groupedSurveys {
|
|
final Map<String, List<dynamic>> map = {};
|
|
for (var s in widget.surveys) {
|
|
final company = s['company']?.toString().trim();
|
|
final key = (company == null || company.isEmpty) ? 'Unknown' : company;
|
|
if (!map.containsKey(key)) {
|
|
map[key] = [];
|
|
}
|
|
map[key]!.add(s);
|
|
}
|
|
return map;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final groups = groupedSurveys;
|
|
final companies = groups.keys.toList()..sort();
|
|
|
|
return Scaffold(
|
|
backgroundColor: AppColors.scaffold,
|
|
appBar: AppBar(
|
|
title: const Text('Competitor Intelligence', style: TextStyle(color: AppColors.darkBlue, fontWeight: FontWeight.w800)),
|
|
backgroundColor: Colors.white,
|
|
iconTheme: const IconThemeData(color: AppColors.darkBlue),
|
|
elevation: 0,
|
|
),
|
|
floatingActionButton: FloatingActionButton.extended(
|
|
onPressed: _isSaving ? null : _showAddSurveyDialog,
|
|
backgroundColor: AppColors.primary,
|
|
icon: const Icon(Icons.add, color: Colors.white),
|
|
label: const Text('Add Provider', style: TextStyle(color: Colors.white, fontWeight: FontWeight.w700)),
|
|
),
|
|
body: _isSaving
|
|
? const Center(child: CircularProgressIndicator())
|
|
: widget.surveys.isEmpty
|
|
? const Center(child: Text('No surveys available.', style: TextStyle(color: AppColors.textSecondary)))
|
|
: CustomScrollView(
|
|
slivers: [
|
|
SliverToBoxAdapter(
|
|
child: Container(
|
|
margin: const EdgeInsets.fromLTRB(16, 16, 16, 4),
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
gradient: const LinearGradient(
|
|
colors: [AppColors.blueLight, Colors.white],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(color: AppColors.primary.withValues(alpha: 0.1)),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(10),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.primary.withValues(alpha: 0.1),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: const Icon(Icons.radar_outlined, color: AppColors.primary, size: 24),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text('Market Overview', style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18, color: AppColors.darkBlue)),
|
|
const SizedBox(height: 2),
|
|
Text('Tracking ${widget.surveys.length} surveys across ${companies.length} providers', style: const TextStyle(fontSize: 13, color: AppColors.textSecondary)),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildStatCard('Active Providers', companies.length.toString(), Icons.storefront),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: _buildStatCard('Total Branches', widget.surveys.length.toString(), Icons.location_on_outlined),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
SliverPadding(
|
|
padding: const EdgeInsets.all(16),
|
|
sliver: SliverList(
|
|
delegate: SliverChildBuilderDelegate(
|
|
(context, index) {
|
|
final company = companies[index];
|
|
final locations = groups[company] ?? [];
|
|
final initial = company.isNotEmpty ? company.substring(0, 1).toUpperCase() : 'U';
|
|
|
|
return Card(
|
|
margin: const EdgeInsets.only(bottom: 16),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
side: BorderSide(color: Colors.grey.shade200, width: 1),
|
|
),
|
|
elevation: 0,
|
|
color: Colors.white,
|
|
child: ExpansionTile(
|
|
shape: const Border(),
|
|
tilePadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
leading: Container(
|
|
width: 48,
|
|
height: 48,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.primary.withValues(alpha: 0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
alignment: Alignment.center,
|
|
child: Text(
|
|
initial,
|
|
style: const TextStyle(color: AppColors.primary, fontSize: 20, fontWeight: FontWeight.w800),
|
|
),
|
|
),
|
|
title: Text(
|
|
company,
|
|
style: const TextStyle(fontWeight: FontWeight.w800, color: AppColors.darkBlue, fontSize: 16),
|
|
),
|
|
subtitle: Row(
|
|
children: [
|
|
const Icon(Icons.storefront, size: 14, color: AppColors.textSecondary),
|
|
const SizedBox(width: 4),
|
|
Text('${locations.length} Locations Surveyed', style: const TextStyle(color: AppColors.textSecondary, fontSize: 13)),
|
|
],
|
|
),
|
|
children: [
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade50,
|
|
border: Border(top: BorderSide(color: Colors.grey.shade200)),
|
|
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(16)),
|
|
),
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
children: locations.map((survey) {
|
|
return _SurveyLocationCard(
|
|
survey: survey as Map<String, dynamic>,
|
|
onEdit: () => _showEditSurveyDialog(survey),
|
|
onDelete: () => _deleteSurvey(survey['id'].toString()),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
childCount: companies.length,
|
|
),
|
|
),
|
|
),
|
|
const SliverToBoxAdapter(child: SizedBox(height: 80)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SurveyLocationCard extends StatefulWidget {
|
|
final Map<String, dynamic> survey;
|
|
final VoidCallback onEdit;
|
|
final VoidCallback onDelete;
|
|
|
|
const _SurveyLocationCard({
|
|
required this.survey,
|
|
required this.onEdit,
|
|
required this.onDelete,
|
|
});
|
|
|
|
@override
|
|
State<_SurveyLocationCard> createState() => _SurveyLocationCardState();
|
|
}
|
|
|
|
class _SurveyLocationCardState extends State<_SurveyLocationCard> {
|
|
bool _isExpanded = false;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final survey = widget.survey;
|
|
final address = survey['address']?.toString() ?? survey['area']?.toString() ?? 'No address';
|
|
final area = survey['area']?.toString() ?? 'Unknown';
|
|
final phone = survey['phone']?.toString() ?? 'N/A';
|
|
final rate = survey['rate_per_kg']?.toString() ?? '';
|
|
final hasRate = rate.isNotEmpty;
|
|
|
|
final pickup = survey['offers_pickup']?.toString().toLowerCase() == 'yes' ? 'Yes' : '—';
|
|
final drop = survey['offers_drop']?.toString().toLowerCase() == 'yes' ? 'Yes' : '—';
|
|
final packing = survey['packing_charge']?.toString() ?? '—';
|
|
final time = survey['time_in_days']?.toString() ?? '—';
|
|
final freq = survey['frequency']?.toString() ?? '—';
|
|
final plusCode = survey['plus_code']?.toString() ?? '—';
|
|
final pincodes = survey['pincodes']?.toString() ?? '—';
|
|
final company = survey['company']?.toString() ?? '—';
|
|
|
|
return Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: Colors.grey.shade200),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
// Header Row
|
|
InkWell(
|
|
onTap: () => setState(() => _isExpanded = !_isExpanded),
|
|
borderRadius: BorderRadius.circular(12),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(12),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Icon(Icons.place_outlined, color: AppColors.primary, size: 20),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
area,
|
|
style: const TextStyle(fontWeight: FontWeight.w700, color: AppColors.darkBlue, fontSize: 14),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
const SizedBox(height: 2),
|
|
FittedBox(
|
|
fit: BoxFit.scaleDown,
|
|
alignment: Alignment.centerLeft,
|
|
child: Row(
|
|
children: [
|
|
const Icon(Icons.phone, size: 12, color: AppColors.textSecondary),
|
|
const SizedBox(width: 4),
|
|
Text(phone, style: const TextStyle(color: AppColors.textSecondary, fontSize: 12)),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
const Text('RATE PER KG', style: TextStyle(fontSize: 10, fontWeight: FontWeight.w800, color: AppColors.textSecondary)),
|
|
const SizedBox(height: 4),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: hasRate ? AppColors.green.withValues(alpha: 0.1) : Colors.grey.shade100,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
child: Text(
|
|
hasRate ? '₹$rate' : 'Not Answered',
|
|
style: TextStyle(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w700,
|
|
color: hasRate ? AppColors.greenDark : AppColors.textSecondary
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
if (!_isExpanded)
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(12, 0, 12, 12),
|
|
child: Column(
|
|
children: [
|
|
const Divider(height: 1),
|
|
const SizedBox(height: 8),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
const Text('LOGISTICS', style: TextStyle(fontSize: 10, fontWeight: FontWeight.w800, color: AppColors.textSecondary)),
|
|
Row(
|
|
children: [
|
|
TextButton(
|
|
onPressed: () => setState(() => _isExpanded = true),
|
|
style: TextButton.styleFrom(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0),
|
|
minimumSize: const Size(0, 30),
|
|
),
|
|
child: const Text('More Info', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w700, color: AppColors.darkBlue)),
|
|
),
|
|
const SizedBox(width: 8),
|
|
InkWell(
|
|
onTap: widget.onEdit,
|
|
child: const Padding(padding: EdgeInsets.all(4.0), child: Icon(Icons.edit_outlined, size: 18, color: AppColors.textSecondary)),
|
|
),
|
|
const SizedBox(width: 4),
|
|
InkWell(
|
|
onTap: widget.onDelete,
|
|
child: const Padding(padding: EdgeInsets.all(4.0), child: Icon(Icons.delete_outline, size: 18, color: AppColors.primaryDark)),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
if (_isExpanded)
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(12)),
|
|
border: Border(top: BorderSide(color: Colors.grey.shade200)),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
TextButton.icon(
|
|
onPressed: () => setState(() => _isExpanded = false),
|
|
style: TextButton.styleFrom(
|
|
backgroundColor: AppColors.primary.withValues(alpha: 0.05),
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
minimumSize: const Size(0, 30),
|
|
),
|
|
icon: const Text('Hide Info', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w700, color: AppColors.primary)),
|
|
label: const Icon(Icons.expand_less, size: 16, color: AppColors.primary),
|
|
),
|
|
const SizedBox(width: 12),
|
|
InkWell(
|
|
onTap: widget.onEdit,
|
|
child: const Padding(padding: EdgeInsets.all(4.0), child: Icon(Icons.edit_outlined, size: 18, color: AppColors.textSecondary)),
|
|
),
|
|
const SizedBox(width: 4),
|
|
InkWell(
|
|
onTap: widget.onDelete,
|
|
child: const Padding(padding: EdgeInsets.all(4.0), child: Icon(Icons.delete_outline, size: 18, color: AppColors.primaryDark)),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
// Grid of Details
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Expanded(
|
|
child: _buildDetailSection(
|
|
icon: Icons.assignment_outlined,
|
|
title: 'ENQUIRY DETAILS',
|
|
items: [
|
|
_buildInfoItem('RATE PER KG', hasRate ? '₹$rate' : 'Not Answered', isHighlight: hasRate),
|
|
_buildInfoItem('PICKUP', pickup),
|
|
_buildInfoItem('DROP', drop),
|
|
_buildInfoItem('PACKING', packing),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: _buildDetailSection(
|
|
icon: Icons.local_shipping_outlined,
|
|
title: 'LOGISTICS & OPS',
|
|
items: [
|
|
_buildInfoItem('TIME IN DAYS', time),
|
|
_buildInfoItem('COMPANY', company),
|
|
_buildInfoItem('FREQUENCY', freq),
|
|
_buildInfoItem('CONTACT NUMBER', phone),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
_buildDetailSection(
|
|
icon: Icons.place_outlined,
|
|
title: 'LOCATION & PINCODE',
|
|
items: [
|
|
Row(
|
|
children: [
|
|
Expanded(child: _buildInfoItem('AREA / ZONE', area)),
|
|
Expanded(child: _buildInfoItem('PLUS CODE', plusCode)),
|
|
],
|
|
),
|
|
_buildInfoItem('SERVICEABLE PINCODES', pincodes),
|
|
_buildInfoItem('FULL ADDRESS', address),
|
|
const SizedBox(height: 8),
|
|
TextButton.icon(
|
|
onPressed: () {},
|
|
icon: const Icon(Icons.map_outlined, size: 14, color: AppColors.primary),
|
|
label: const Text('View On Map', style: TextStyle(color: AppColors.primary, fontSize: 12, fontWeight: FontWeight.w700)),
|
|
style: TextButton.styleFrom(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8), side: const BorderSide(color: AppColors.primary)),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
_buildDetailSection(
|
|
icon: Icons.list_alt,
|
|
title: 'RECORD METADATA',
|
|
items: [
|
|
Row(
|
|
children: [
|
|
Expanded(child: _buildInfoItem('CREATED BY', 'System')),
|
|
Expanded(child: _buildInfoItem('LAST EDITED BY', '—')),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDetailSection({required IconData icon, required String title, required List<Widget> items}) {
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(color: Colors.grey.shade200),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
decoration: const BoxDecoration(
|
|
color: Color(0xFFFDECEE),
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(8)),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(icon, size: 14, color: AppColors.primary),
|
|
const SizedBox(width: 6),
|
|
Expanded(
|
|
child: FittedBox(
|
|
alignment: Alignment.centerLeft,
|
|
fit: BoxFit.scaleDown,
|
|
child: Text(title, style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w800, color: AppColors.darkBlue, letterSpacing: 0.5)),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(12),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: items.expand((w) => [w, const SizedBox(height: 12)]).toList()..removeLast(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildInfoItem(String label, String value, {bool isHighlight = false}) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(label, style: const TextStyle(fontSize: 9, fontWeight: FontWeight.w800, color: AppColors.textSecondary, letterSpacing: 0.5)),
|
|
const SizedBox(height: 4),
|
|
if (isHighlight)
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
|
decoration: BoxDecoration(color: AppColors.green.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4)),
|
|
child: Text(value, style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w700, color: AppColors.greenDark)),
|
|
)
|
|
else
|
|
Text(value, style: const TextStyle(fontSize: 12, color: AppColors.darkBlue, fontWeight: FontWeight.w600)),
|
|
],
|
|
);
|
|
}
|
|
}
|