Your changes

This commit is contained in:
divya abdar
2025-12-11 18:36:11 +05:30
parent 3bf27cc29d
commit 9faf983b95
36 changed files with 4677 additions and 1046 deletions

View File

@@ -12,250 +12,572 @@ class DashboardScreen extends StatefulWidget {
State<DashboardScreen> createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> {
class _DashboardScreenState extends State<DashboardScreen>
with TickerProviderStateMixin {
late AnimationController _scaleCtrl;
late AnimationController _shineCtrl;
@override
void initState() {
super.initState();
_scaleCtrl = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1200),
lowerBound: 0.97,
upperBound: 1.0,
)..repeat(reverse: true);
_shineCtrl = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
)..repeat();
WidgetsBinding.instance.addPostFrameCallback((_) async {
final auth = Provider.of<AuthProvider>(context, listen: false);
// STEP 1: Try refresh token BEFORE any API calls
await auth.tryRefreshToken(context);
// STEP 2: Now safe to load dashboard
final dash = Provider.of<DashboardProvider>(context, listen: false);
dash.init(context);
await dash.loadSummary(context);
// STEP 3: Load marks AFTER refresh
final marks = Provider.of<MarkListProvider>(context, listen: false);
marks.init(context);
await marks.loadMarks(context);
});
}
@override
void dispose() {
_scaleCtrl.dispose();
_shineCtrl.dispose();
super.dispose();
}
void _showAddMarkForm() {
// ============================================================
// CENTERED ADD MARK POPUP
// ============================================================
void _showAddMarkForm(double scale) {
final markCtrl = TextEditingController();
final originCtrl = TextEditingController();
final destCtrl = TextEditingController();
showModalBottomSheet(
showDialog(
context: context,
isScrollControlled: true,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
barrierDismissible: true,
builder: (_) {
return Padding(
padding: EdgeInsets.fromLTRB(
18,
18,
18,
MediaQuery.of(context).viewInsets.bottom + 20,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
"Add Mark No",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
return Center(
child: Material(
color: Colors.transparent,
child: Container(
width: MediaQuery.of(context).size.width * 0.88,
padding: EdgeInsets.all(20 * scale),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20 * scale),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 16,
offset: const Offset(0, 6),
),
],
),
const SizedBox(height: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Add Mark No",
style: TextStyle(
fontSize: 20 * scale,
fontWeight: FontWeight.bold,
color: Colors.indigo,
),
),
TextField(controller: markCtrl, decoration: const InputDecoration(labelText: "Mark No")),
const SizedBox(height: 12),
SizedBox(height: 18 * scale),
TextField(controller: originCtrl, decoration: const InputDecoration(labelText: "Origin")),
const SizedBox(height: 12),
_inputField(markCtrl, "Mark No", scale),
SizedBox(height: 12 * scale),
_inputField(originCtrl, "Origin", scale),
SizedBox(height: 12 * scale),
_inputField(destCtrl, "Destination", scale),
TextField(controller: destCtrl, decoration: const InputDecoration(labelText: "Destination")),
const SizedBox(height: 20),
SizedBox(height: 22 * scale),
ElevatedButton(
onPressed: () async {
final mark = markCtrl.text.trim();
final origin = originCtrl.text.trim();
final dest = destCtrl.text.trim();
SizedBox(
width: double.infinity,
child: DecoratedBox(
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Colors.indigo, Colors.deepPurple],
),
borderRadius: BorderRadius.circular(12 * scale),
),
child: ElevatedButton(
onPressed: () async {
final mark = markCtrl.text.trim();
final origin = originCtrl.text.trim();
final dest = destCtrl.text.trim();
if (mark.isEmpty || origin.isEmpty || dest.isEmpty) {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(content: Text("All fields required")));
return;
}
if (mark.isEmpty || origin.isEmpty || dest.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("All fields are required")),
);
return;
}
final provider = Provider.of<MarkListProvider>(context, listen: false);
final res = await provider.addMark(context, mark, origin, dest);
final provider =
Provider.of<MarkListProvider>(context, listen: false);
final res =
await provider.addMark(context, mark, origin, dest);
if (res['success'] == true) {
Navigator.pop(context);
} else {
final msg = res['message'] ?? "Failed";
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
}
},
child: const Text("Submit"),
if (res['success'] == true) {
await Provider.of<MarkListProvider>(context,
listen: false)
.loadMarks(context);
Navigator.pop(context);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(res['message'] ?? "Failed")),
);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
padding: EdgeInsets.symmetric(vertical: 14 * scale),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12 * scale),
),
),
child: Text(
"Submit",
style: TextStyle(
fontSize: 16 * scale,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
),
SizedBox(height: 6 * scale),
],
),
],
),
),
);
},
);
}
// ============================================================
// MAIN UI (Responsive)
// ============================================================
@override
Widget build(BuildContext context) {
final auth = Provider.of<AuthProvider>(context);
final dash = Provider.of<DashboardProvider>(context);
final marks = Provider.of<MarkListProvider>(context);
final name = auth.user?['customer_name'] ?? 'User';
if (dash.loading) {
return const Center(child: CircularProgressIndicator());
}
final name = auth.user?['customer_name'] ?? "User";
final width = MediaQuery.of(context).size.width;
final scale = (width / 430).clamp(0.88, 1.08);
return SingleChildScrollView(
padding: const EdgeInsets.all(18),
padding: EdgeInsets.all(16 * scale),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// HEADER
Text(
"Welcome, $name 👋",
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w600),
// WELCOME CARD WITH ANIMATION
AnimatedBuilder(
animation: _scaleCtrl,
builder: (_, __) {
return Transform.scale(
scale: _scaleCtrl.value,
child: Stack(
children: [
Container(
padding: EdgeInsets.all(20 * scale),
width: double.infinity,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFFFFA726), Color(0xFFFFEB3B)],
),
borderRadius: BorderRadius.circular(18 * scale),
),
child: Text(
"Welcome, $name 👋",
style: TextStyle(
fontSize: 24 * scale,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
// Shine Animation
AnimatedBuilder(
animation: _shineCtrl,
builder: (_, __) {
final left = _shineCtrl.value *
(MediaQuery.of(context).size.width + 140) -
140;
return Positioned(
left: left,
top: -40 * scale,
bottom: -40 * scale,
child: Transform.rotate(
angle: -0.45,
child: Container(
width: 120 * scale,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.white.withOpacity(0),
Colors.white.withOpacity(.3),
Colors.white.withOpacity(0),
],
),
),
),
),
);
},
)
],
),
);
},
),
const SizedBox(height: 20),
// ORDER SUMMARY
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_statBox("Active", dash.activeOrders, Colors.blue),
_statBox("In Transit", dash.inTransitOrders, Colors.orange),
_statBox("Delivered", dash.deliveredOrders, Colors.green),
],
SizedBox(height: 18 * scale),
_summarySection(
dash,
rawAmount: "${dash.totalRaw ?? 0}",
scale: scale,
),
const SizedBox(height: 20),
_valueCard("Total Value", dash.totalValue),
const SizedBox(height: 10),
_valueCard("Raw Amount", "${dash.totalRaw.toStringAsFixed(2)}"),
const SizedBox(height: 30),
SizedBox(height: 26 * scale),
// ADD + VIEW ALL BUTTONS SIDE BY SIDE
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ElevatedButton.icon(
icon: const Icon(Icons.add),
label: const Text("Add Mark No"),
onPressed: _showAddMarkForm,
),
if (marks.marks.length > 0)
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const MarkListScreen()),
);
},
child: const Text(
"View All →",
style: TextStyle(
fontSize: 16,
color: Colors.indigo,
fontWeight: FontWeight.w600,
// MARK LIST CARD
Container(
padding: EdgeInsets.all(16 * scale),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14 * scale),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(.06),
blurRadius: 8 * scale,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Add Mark Button
Center(
child: SizedBox(
width: width * 0.95,
child: DecoratedBox(
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Colors.indigo, Colors.deepPurple],
),
borderRadius: BorderRadius.circular(12 * scale),
),
child: ElevatedButton(
onPressed: () => _showAddMarkForm(scale),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
padding: EdgeInsets.symmetric(vertical: 14 * scale),
),
child: Text(
"Add Mark No",
style: TextStyle(
fontSize: 16 * scale,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
),
),
SizedBox(height: 16 * scale),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Latest Mark Numbers",
style: TextStyle(
fontSize: 18 * scale,
fontWeight: FontWeight.bold,
),
),
if (marks.marks.isNotEmpty)
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const MarkListScreen()),
);
},
child: Text(
"View All →",
style: TextStyle(
fontSize: 15 * scale,
color: Colors.indigo,
fontWeight: FontWeight.bold,
),
),
),
],
),
SizedBox(height: 12 * scale),
// Scrollable Mark List
SizedBox(
height: 300 * scale,
child: Scrollbar(
thumbVisibility: true,
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: marks.marks.length,
itemBuilder: (context, i) =>
_markTile(marks.marks[i], scale),
),
),
),
],
),
),
SizedBox(height: 40 * scale),
],
),
);
}
// ============================================================
// SUMMARY SECTION
// ============================================================
Widget _summarySection(dash,
{required String rawAmount, required double scale}) {
return Container(
padding: EdgeInsets.all(14 * scale),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14 * scale),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(.06),
blurRadius: 8 * scale,
),
],
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_summaryTile("Active Orders", dash.activeOrders ?? 0,
Colors.blue, Icons.inventory, scale),
_summaryTile("In Transit", dash.inTransitOrders ?? 0,
Colors.orange, Icons.local_shipping, scale),
],
),
const SizedBox(height: 20),
SizedBox(height: 12 * scale),
// MARK LIST (only 10 latest)
const Text(
"Latest Mark Numbers",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_summaryTile("Delivered", dash.deliveredOrders ?? 0,
Colors.green, Icons.check_circle, scale),
_summaryTile("Total Value", "${dash.totalValue ?? 0}",
Colors.teal, Icons.money, scale),
],
),
const SizedBox(height: 10),
SizedBox(height: 16 * scale),
if (marks.loading)
const Center(child: CircularProgressIndicator())
else
Column(
children: List.generate(
marks.marks.length > 10 ? 10 : marks.marks.length,
(i) {
final m = marks.marks[i];
return Card(
child: ListTile(
title: Text(m['mark_no']),
subtitle: Text("${m['origin']}${m['destination']}"),
trailing: Text(
m['status'],
style: const TextStyle(color: Colors.indigo),
),
),
);
},
_rawAmountTile("Raw Amount", rawAmount, scale),
],
),
);
}
Widget _summaryTile(String title, dynamic value, Color color,
IconData icon, double scale) {
return Container(
width: MediaQuery.of(context).size.width * 0.41,
padding: EdgeInsets.all(14 * scale),
decoration: BoxDecoration(
color: color.withOpacity(.12),
borderRadius: BorderRadius.circular(14 * scale),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
value.toString(),
style: TextStyle(
fontSize: 20 * scale,
fontWeight: FontWeight.bold,
color: color,
),
),
SizedBox(height: 2 * scale),
SizedBox(
width: 100 * scale,
child: Text(
title,
style: TextStyle(
fontSize: 13 * scale,
fontWeight: FontWeight.w600,
),
),
),
],
),
Icon(icon, size: 28 * scale, color: color),
],
),
);
}
Widget _rawAmountTile(String title, String value, double scale) {
return Container(
padding: EdgeInsets.all(14 * scale),
decoration: BoxDecoration(
color: Colors.deepPurple.shade50,
borderRadius: BorderRadius.circular(14 * scale),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
value,
style: TextStyle(
fontSize: 20 * scale,
color: Colors.deepPurple,
fontWeight: FontWeight.bold,
),
),
Text(
title,
style: TextStyle(
fontSize: 13 * scale,
color: Colors.grey,
),
),
],
),
Icon(Icons.currency_rupee,
size: 28 * scale, color: Colors.deepPurple),
],
),
);
}
// ============================================================
// MARK TILE
// ============================================================
Widget _markTile(dynamic m, double scale) {
return Container(
margin: EdgeInsets.only(bottom: 12 * scale),
padding: EdgeInsets.all(14 * scale),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF42A5F5), Color(0xFF80DEEA)],
),
borderRadius: BorderRadius.circular(14 * scale),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
m['mark_no'],
style: TextStyle(
fontSize: 18 * scale,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
Text(
"${m['origin']}${m['destination']}",
style: TextStyle(
fontSize: 14 * scale,
color: Colors.white,
),
),
],
),
Container(
padding: EdgeInsets.symmetric(
horizontal: 10 * scale,
vertical: 6 * scale,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(.2),
borderRadius: BorderRadius.circular(8 * scale),
),
child: Text(
m['status'],
style: TextStyle(
fontSize: 12 * scale,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 30),
],
),
);
}
// UI WIDGETS
Widget _statBox(String title, int value, Color color) {
return Container(
width: 110,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: color.withOpacity(0.15),
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
Text(value.toString(),
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(title, style: const TextStyle(fontSize: 14)),
],
),
);
}
Widget _valueCard(String title, String value) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: Colors.indigo.shade50,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style: const TextStyle(
fontSize: 14, fontWeight: FontWeight.w600, color: Colors.grey)),
const SizedBox(height: 6),
Text(
value,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.indigo,
),
),
],
),
);
}
// ============================================================
// INPUT FIELD
// ============================================================
Widget _inputField(TextEditingController controller, String label, double scale) {
return TextField(
controller: controller,
style: TextStyle(fontSize: 15 * scale),
decoration: InputDecoration(
labelText: label,
labelStyle: TextStyle(fontSize: 14 * scale),
filled: true,
fillColor: Colors.lightBlue.shade50,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12 * scale),
borderSide: BorderSide.none,
),
contentPadding:
EdgeInsets.symmetric(horizontal: 14 * scale, vertical: 14 * scale),
),
);
}
}