Some changes
This commit is contained in:
parent
2a72067dd6
commit
8ff5196ab8
19
WORK_LOG.md
19
WORK_LOG.md
@ -1,5 +1,24 @@
|
||||
# Work Log
|
||||
|
||||
## Thursday, December 18, 2025
|
||||
|
||||
### UI Updates
|
||||
- **Admin Station Page (`station.dart`)**
|
||||
- **Layout Refinement**:
|
||||
- Moved Department Name to the title row, aligned to the right side.
|
||||
- Moved Station User PIC (Full Name) to the subtitle row.
|
||||
|
||||
- **Admin Product Page (`product.dart`)**
|
||||
- **Image URL Handling**:
|
||||
- Updated the product image URL construction to use `ApiService.baseUrl` dynamically instead of a hardcoded URL.
|
||||
|
||||
- **Admin Item Page (`item.dart`)**
|
||||
- **Label & Layout Adjustments**:
|
||||
- Renamed `Location` label to **`Station`** (mapped to `currentStation`).
|
||||
- Renamed `User` label to **`Location User`** (mapped to `currentUser`).
|
||||
- Added a new **`Location`** header above the details.
|
||||
- Reordered fields under the Location header to: **Location User**, **Store**, **Station**.
|
||||
|
||||
## Monday, December 15, 2025
|
||||
|
||||
### Features & UI Updates
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -6,6 +6,9 @@ import 'package:inventory_system/screens/bottom_nav_bar.dart';
|
||||
import 'package:inventory_system/screens/nav_bar.dart';
|
||||
import 'package:inventory_system/screens/title_bar.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:pdf/pdf.dart';
|
||||
import 'package:pdf/widgets.dart' as pw;
|
||||
import 'package:printing/printing.dart';
|
||||
|
||||
class ItemScreen extends StatefulWidget {
|
||||
const ItemScreen({super.key});
|
||||
@ -126,6 +129,63 @@ class _ItemScreenState extends State<ItemScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _printQrInfo(Map<String, dynamic> item) async {
|
||||
final doc = pw.Document();
|
||||
|
||||
doc.addPage(
|
||||
pw.Page(
|
||||
pageFormat: PdfPageFormat.a4,
|
||||
build: (pw.Context context) {
|
||||
return pw.Column(
|
||||
mainAxisSize: pw.MainAxisSize.min,
|
||||
children: [
|
||||
pw.Row(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Column(
|
||||
children: [
|
||||
pw.BarcodeWidget(
|
||||
barcode: pw.Barcode.qrCode(),
|
||||
data: item['uniqueID'] ?? '',
|
||||
width: 100,
|
||||
height: 100,
|
||||
),
|
||||
pw.SizedBox(height: 8),
|
||||
pw.Text(
|
||||
item['uniqueID'] ?? '',
|
||||
style: const pw.TextStyle(fontSize: 12), // monospace not strictly required for PDF unless font loaded
|
||||
),
|
||||
],
|
||||
),
|
||||
pw.SizedBox(width: 24),
|
||||
pw.Expanded(
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Text(item['currentStore'] ?? 'N/A', style: const pw.TextStyle(fontSize: 16)),
|
||||
pw.SizedBox(height: 8),
|
||||
pw.Text(item['productShortName'] ?? item['productName'] ?? '', style: const pw.TextStyle(fontSize: 16)),
|
||||
pw.SizedBox(height: 8),
|
||||
pw.Text(item['serialNumber'] ?? '', style: const pw.TextStyle(fontSize: 16)),
|
||||
pw.SizedBox(height: 8),
|
||||
pw.Text(item['partNumber'] ?? '', style: const pw.TextStyle(fontSize: 16)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await Printing.layoutPdf(
|
||||
onLayout: (PdfPageFormat format) async => doc.save(),
|
||||
name: '${item['uniqueID']}_QR.pdf',
|
||||
);
|
||||
}
|
||||
|
||||
void _showPrintDialog(Map<String, dynamic> item) {
|
||||
showDialog(
|
||||
context: context,
|
||||
@ -177,7 +237,7 @@ class _ItemScreenState extends State<ItemScreen> {
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
onPressed: () => _printQrInfo(item),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
foregroundColor: Colors.black87,
|
||||
|
||||
@ -430,9 +430,9 @@ class _ItemMovementAllScreenState extends State<ItemMovementAllScreen>
|
||||
tiles = [
|
||||
_buildDetailItem('Action', item['action'] ?? 'N/A'),
|
||||
_buildDetailItem('Product Category', item['productCategory'] ?? 'N/A'),
|
||||
_buildDetailItem('Status', item['latestStatus'] ?? 'N/A'),
|
||||
_buildDetailItem('Start Status', item['latestStatus'] ?? item['toOther'] ?? 'N/A'),
|
||||
_buildDetailItem('From Station', item['lastStationName'] ?? 'N/A'),
|
||||
_buildDetailItem('From Store', item['lastStoreName'] ?? 'N/A'),
|
||||
_buildDetailItem('From Store', item['lastStoreName'] ?? item['toStoreName'] ?? 'N/A'),
|
||||
_buildDetailItem('Quantity', item['quantity']?.toString() ?? 'N/A'),
|
||||
_buildDetailItem('Last User', item['lastUserName'] ?? 'N/A'),
|
||||
_buildDetailItem('From User', item['toUserName'] ?? 'N/A'),
|
||||
@ -460,7 +460,7 @@ class _ItemMovementAllScreenState extends State<ItemMovementAllScreen>
|
||||
_buildDetailItem('From User', item['toUserName'] ?? 'N/A'),
|
||||
_buildDetailItem('From Store', item['lastStoreName'] ?? 'N/A'),
|
||||
_buildDetailItem('To Store', item['toStoreName'] ?? 'N/A'),
|
||||
_buildDetailItem('Latest Status', item['latestStatus'] ?? 'N/A'),
|
||||
_buildDetailItem('Latest Status', item['latestStatus'] ?? item['toOther'] ?? 'N/A'),
|
||||
];
|
||||
break;
|
||||
}
|
||||
@ -488,7 +488,7 @@ class _ItemMovementAllScreenState extends State<ItemMovementAllScreen>
|
||||
if (note != null && note.toString().trim().isNotEmpty) ...[
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Note / Remark',
|
||||
'Remark',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade600,
|
||||
@ -503,6 +503,7 @@ class _ItemMovementAllScreenState extends State<ItemMovementAllScreen>
|
||||
),
|
||||
),
|
||||
],
|
||||
_buildDocumentTile(item['consignmentNote']),
|
||||
],
|
||||
);
|
||||
},
|
||||
@ -510,6 +511,86 @@ class _ItemMovementAllScreenState extends State<ItemMovementAllScreen>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDocumentTile(String? docPath) {
|
||||
final hasDoc = docPath != null && docPath.isNotEmpty;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Document/Picture',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade600,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
hasDoc
|
||||
? InkWell(
|
||||
onTap: () => _showDocument(docPath),
|
||||
child: Text(
|
||||
'Show',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.blue.shade700,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const Text(
|
||||
'No Document',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showDocument(String docPath) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => Dialog(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
AppBar(
|
||||
title: const Text('Document'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
),
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.black,
|
||||
),
|
||||
InteractiveViewer(
|
||||
child: Image.network(
|
||||
ApiService.baseUrl + docPath,
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (ctx, err, stack) => const Padding(
|
||||
padding: EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(Icons.broken_image, size: 50, color: Colors.grey),
|
||||
Text('Failed to load image'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDetailItem(String label, String value) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
||||
@ -261,7 +261,7 @@ class _ItemMovementItemScreenState extends State<ItemMovementItemScreen> {
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'Status: ${event['latestStatus'] ?? 'N/A'}',
|
||||
'Status: ${event['latestStatus'] ?? event['toOther'] ?? 'N/A'}',
|
||||
style: const TextStyle(fontWeight: FontWeight.w600, color: Colors.black87),
|
||||
),
|
||||
],
|
||||
@ -394,10 +394,37 @@ class _ItemMovementItemScreenState extends State<ItemMovementItemScreen> {
|
||||
}
|
||||
|
||||
Widget _buildMovementDetailCard(Map<String, dynamic> movement) {
|
||||
final statusColor = _statusColor(movement['action'] ?? '');
|
||||
// --- Web App Logic for Labels and Colors ---
|
||||
String toOther = movement['toOther'] ?? '';
|
||||
String action = movement['action'] ?? '';
|
||||
bool isToStation = movement['toStation'] != null;
|
||||
final movementId = movement['id'].toString();
|
||||
final bool isInnerExpanded = _expandedMovements.contains(movementId);
|
||||
|
||||
// 1. Status Heading
|
||||
String statusHeading = 'Assign';
|
||||
Color statusHeadingColor = Colors.cyan; // Default text-info
|
||||
|
||||
if (toOther == 'Return') {
|
||||
statusHeading = 'Return';
|
||||
statusHeadingColor = Colors.orange; // text-warning
|
||||
} else if (toOther == 'On Delivery') {
|
||||
statusHeading = 'Receive';
|
||||
statusHeadingColor = Colors.blue; // text-primary
|
||||
} else if (isToStation) {
|
||||
statusHeading = 'Change';
|
||||
statusHeadingColor = Colors.green; // text-success
|
||||
} else if (toOther == 'Faulty' || toOther == 'Calibration' || toOther == 'Repair') {
|
||||
statusHeading = toOther;
|
||||
statusHeadingColor = Colors.grey.shade600; // text-numb (usually greyish)
|
||||
} else if (action == 'Register') {
|
||||
statusHeading = 'Register';
|
||||
statusHeadingColor = Colors.purple; // text-weird (arbitrary distinct color)
|
||||
} else if (action == 'Assign' && !isToStation) {
|
||||
statusHeading = 'Assign';
|
||||
statusHeadingColor = Colors.cyan; // text-info
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
@ -413,15 +440,15 @@ class _ItemMovementItemScreenState extends State<ItemMovementItemScreen> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
movement['action'] ?? '-',
|
||||
statusHeading,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: statusColor,
|
||||
color: statusHeadingColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
_chip(movement['latestStatus'] ?? 'N/A', Colors.green.shade700),
|
||||
_chip(movement['latestStatus'] ?? movement['toOther'] ?? 'N/A', Colors.green.shade700),
|
||||
const SizedBox(width: 8),
|
||||
_outlinedButton(
|
||||
isInnerExpanded ? 'Hide Details' : 'Show Details',
|
||||
@ -451,9 +478,9 @@ class _ItemMovementItemScreenState extends State<ItemMovementItemScreen> {
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _actionButton('Remark', isPrimary: true)),
|
||||
Expanded(child: _actionButton('Remark', isPrimary: true, onTap: () => _showRemark(movement['remark']))),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: _actionButton('Consignment Note')),
|
||||
Expanded(child: _actionButton('Consignment Note', isPrimary: true, color: Colors.teal.shade600, onTap: () => _showDocument(movement['consignmentNote']))),
|
||||
],
|
||||
),
|
||||
],
|
||||
@ -465,6 +492,66 @@ class _ItemMovementItemScreenState extends State<ItemMovementItemScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
void _showRemark(String? remark) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Remark'),
|
||||
content: Text(remark?.isNotEmpty == true ? remark! : 'No remark available.'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Close'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showDocument(String? docPath) {
|
||||
if (docPath == null || docPath.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('No document available')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => Dialog(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
AppBar(
|
||||
title: const Text('Document'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
),
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.black,
|
||||
),
|
||||
InteractiveViewer(
|
||||
child: Image.network(
|
||||
ApiService.baseUrl + docPath,
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (ctx, err, stack) => const Padding(
|
||||
padding: EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(Icons.broken_image, size: 50, color: Colors.grey),
|
||||
Text('Failed to load image'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMovementFlow(Map<String, dynamic> movement) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
@ -620,12 +707,12 @@ class _ItemMovementItemScreenState extends State<ItemMovementItemScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _actionButton(String text, {bool isPrimary = false}) {
|
||||
Widget _actionButton(String text, {bool isPrimary = false, Color? color, VoidCallback? onTap}) {
|
||||
return ElevatedButton(
|
||||
onPressed: () {},
|
||||
onPressed: onTap,
|
||||
style: isPrimary
|
||||
? ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue.shade600,
|
||||
backgroundColor: color ?? Colors.blue.shade600,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
|
||||
@ -257,10 +257,11 @@ class _ItemMovementStationScreenState extends State<ItemMovementStationScreen> {
|
||||
border: Border(top: BorderSide(color: Colors.grey.shade300)),
|
||||
),
|
||||
child: DataTable(
|
||||
columnSpacing: 20,
|
||||
columnSpacing: 10,
|
||||
horizontalMargin: 10,
|
||||
headingRowHeight: 40,
|
||||
dataRowMinHeight: 48,
|
||||
dataRowMaxHeight: 56,
|
||||
dataRowMaxHeight: 80,
|
||||
headingTextStyle: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
@ -278,8 +279,17 @@ class _ItemMovementStationScreenState extends State<ItemMovementStationScreen> {
|
||||
return DataRow(
|
||||
cells: [
|
||||
DataCell(Text(item['code']!)),
|
||||
DataCell(Text(item['description']!)),
|
||||
DataCell(Text(formattedDate)),
|
||||
DataCell(
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 180),
|
||||
child: Text(
|
||||
item['description']!,
|
||||
softWrap: true,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(Text(formattedDate, style: const TextStyle(fontSize: 12))),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:inventory_system/services/api_service.dart'; // 1. Import ApiService
|
||||
import 'package:inventory_system/services/api_service.dart';
|
||||
import 'package:inventory_system/services/auth_service.dart';
|
||||
import 'package:inventory_system/screens/admin/home_screen/home_screen.dart';
|
||||
import 'package:inventory_system/screens/user/home_screen/home_screen_user.dart';
|
||||
import 'package:inventory_system/routes/slide_route.dart';
|
||||
import 'package:inventory_system/services/session_manager.dart';
|
||||
import 'package:inventory_system/services/session_manager.dart'; //To set the server URL
|
||||
|
||||
class LoginScreen extends StatefulWidget {
|
||||
const LoginScreen({super.key});
|
||||
@ -70,7 +70,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
// 2. ADD THIS METHOD to show the URL settings dialog
|
||||
// To show the URL settings dialog
|
||||
void _showUrlDialog(BuildContext context) {
|
||||
final TextEditingController urlController = TextEditingController();
|
||||
urlController.text = ApiService.baseUrl; // Set current URL
|
||||
@ -119,9 +119,8 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Stack( // 3. Use a Stack to overlay the settings button
|
||||
body: Stack( // Use a Stack to overlay the settings button
|
||||
children: [
|
||||
// This is your existing UI
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
|
||||
@ -40,13 +40,13 @@ class NavBar extends StatelessWidget {
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final currentUser = SessionManager.instance.currentUser;
|
||||
final String displayName = currentUser?['fullName'] ?? 'Not Logged In';
|
||||
final String displayName = currentUser?['fullName'] ?? 'Not Logged In'; //Display logged in user
|
||||
|
||||
final bool isSuperAdmin = currentUser?['isSuperAdmin'] ?? false;
|
||||
final bool isInventoryMaster = currentUser?['isInventoryMaster'] ?? false;
|
||||
|
||||
String userRole = 'User';
|
||||
if (isSuperAdmin) {
|
||||
if (isSuperAdmin) { //To determine and display role of user
|
||||
userRole = 'Administrator';
|
||||
} else if (isInventoryMaster) {
|
||||
userRole = 'Inventory Master';
|
||||
@ -97,12 +97,12 @@ class NavBar extends StatelessWidget {
|
||||
else
|
||||
..._buildUserListTiles(context),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.settings),
|
||||
title: const Text('Settings'),
|
||||
selected: selectedScreen == AppScreen.settings,
|
||||
onTap: () {},
|
||||
),
|
||||
// ListTile(
|
||||
// leading: const Icon(Icons.settings),
|
||||
// title: const Text('Settings'),
|
||||
// selected: selectedScreen == AppScreen.settings,
|
||||
// onTap: () {},
|
||||
// ),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.logout),
|
||||
title: const Text('Logout'),
|
||||
@ -116,6 +116,7 @@ class NavBar extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// Admin/Inventory Master Navigation Bar
|
||||
List<Widget> _buildAdminListTiles(BuildContext context) {
|
||||
return [
|
||||
ListTile(
|
||||
@ -209,6 +210,7 @@ class NavBar extends StatelessWidget {
|
||||
];
|
||||
}
|
||||
|
||||
// User Nav Bar
|
||||
List<Widget> _buildUserListTiles(BuildContext context) {
|
||||
return [
|
||||
ListTile(
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'dart:async';
|
||||
|
||||
class SplashScreen extends StatefulWidget {
|
||||
@ -97,7 +98,7 @@ class _SplashScreenState extends State<SplashScreen> with SingleTickerProviderSt
|
||||
child: Text(
|
||||
'INVENTORY\nSYSTEM',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
style: GoogleFonts.roboto(
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.w900,
|
||||
letterSpacing: 4,
|
||||
|
||||
@ -456,9 +456,9 @@ class _ItemMovementAllUserScreenState extends State<ItemMovementAllUserScreen>
|
||||
tiles = [
|
||||
_buildDetailItem('Action', item['action'] ?? 'N/A'),
|
||||
_buildDetailItem('Product Category', item['productCategory'] ?? 'N/A'), // Use productCategory
|
||||
_buildDetailItem('Status', item['latestStatus'] ?? 'N/A'), // Use latestStatus
|
||||
_buildDetailItem('Start Status', item['latestStatus'] ?? item['toOther'] ?? 'N/A'), // Use latestStatus
|
||||
_buildDetailItem('From Station', item['lastStationName'] ?? 'N/A'),
|
||||
_buildDetailItem('From Store', item['lastStoreName'] ?? 'N/A'),
|
||||
_buildDetailItem('From Store', item['lastStoreName'] ?? item['toStoreName'] ?? 'N/A'),
|
||||
_buildDetailItem('Quantity', item['quantity']?.toString() ?? 'N/A'),
|
||||
_buildDetailItem('From User', item['lastUserName'] ?? 'N/A'),
|
||||
_buildDetailItem('To User', item['toUserName'] ?? 'N/A'),
|
||||
@ -485,7 +485,7 @@ class _ItemMovementAllUserScreenState extends State<ItemMovementAllUserScreen>
|
||||
_buildDetailItem('To User', item['toUserName'] ?? 'N/A'),
|
||||
_buildDetailItem('From Store', item['lastStoreName'] ?? 'N/A'),
|
||||
_buildDetailItem('To Store', item['toStoreName'] ?? 'N/A'),
|
||||
_buildDetailItem('Latest Status', item['latestStatus'] ?? 'N/A'),
|
||||
_buildDetailItem('Latest Status', item['latestStatus'] ?? item['toOther'] ?? 'N/A'),
|
||||
];
|
||||
break;
|
||||
}
|
||||
@ -528,6 +528,7 @@ class _ItemMovementAllUserScreenState extends State<ItemMovementAllUserScreen>
|
||||
),
|
||||
),
|
||||
],
|
||||
_buildDocumentTile(item['consignmentNote']),
|
||||
],
|
||||
);
|
||||
},
|
||||
@ -535,6 +536,86 @@ class _ItemMovementAllUserScreenState extends State<ItemMovementAllUserScreen>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDocumentTile(String? docPath) {
|
||||
final hasDoc = docPath != null && docPath.isNotEmpty;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Document/Picture',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade600,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
hasDoc
|
||||
? InkWell(
|
||||
onTap: () => _showDocument(docPath),
|
||||
child: Text(
|
||||
'Show',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.blue.shade700,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const Text(
|
||||
'No Document',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showDocument(String docPath) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => Dialog(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
AppBar(
|
||||
title: const Text('Document'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
),
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.black,
|
||||
),
|
||||
InteractiveViewer(
|
||||
child: Image.network(
|
||||
ApiService.baseUrl + docPath,
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (ctx, err, stack) => const Padding(
|
||||
padding: EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(Icons.broken_image, size: 50, color: Colors.grey),
|
||||
Text('Failed to load image'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// A helper widget to display a label and its value.
|
||||
Widget _buildDetailItem(String label, String value) {
|
||||
return Column(
|
||||
|
||||
@ -402,21 +402,26 @@ class _ItemMovementItemUserScreenState extends State<ItemMovementItemUserScreen>
|
||||
|
||||
// 1. Status Heading
|
||||
String statusHeading = 'Assign';
|
||||
Color statusHeadingColor = Colors.blue;
|
||||
Color statusHeadingColor = Colors.cyan; // Default text-info
|
||||
|
||||
if (toOther == 'Return') {
|
||||
statusHeading = 'Return';
|
||||
statusHeadingColor = Colors.orange; // Web: text-warning
|
||||
statusHeadingColor = Colors.orange; // text-warning
|
||||
} else if (toOther == 'On Delivery') {
|
||||
statusHeading = 'Receive';
|
||||
statusHeadingColor = Colors.blue; // Web: text-primary
|
||||
statusHeadingColor = Colors.blue; // text-primary
|
||||
} else if (isToStation) {
|
||||
statusHeading = 'Change';
|
||||
statusHeadingColor = Colors.green; // Web: text-success
|
||||
} else {
|
||||
// Default 'Assign' (text-info in web, usually light blue/cyan)
|
||||
statusHeadingColor = Colors.green; // text-success
|
||||
} else if (toOther == 'Faulty' || toOther == 'Calibration' || toOther == 'Repair') {
|
||||
statusHeading = toOther;
|
||||
statusHeadingColor = Colors.grey.shade600; // text-numb
|
||||
} else if (action == 'Register') {
|
||||
statusHeading = 'Register';
|
||||
statusHeadingColor = Colors.purple; // text-weird
|
||||
} else if (action == 'Assign' && !isToStation) {
|
||||
statusHeading = 'Assign';
|
||||
statusHeadingColor = Colors.cyan;
|
||||
statusHeadingColor = Colors.cyan; // text-info
|
||||
}
|
||||
|
||||
// 2. Completion Status
|
||||
@ -511,9 +516,9 @@ class _ItemMovementItemUserScreenState extends State<ItemMovementItemUserScreen>
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _actionButton('Remark', isPrimary: true)),
|
||||
Expanded(child: _actionButton('Remark', isPrimary: true, onTap: () => _showRemark(movement['remark']))),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: _actionButton('Consignment Note')),
|
||||
Expanded(child: _actionButton('Consignment Note', isPrimary: true, color: Colors.teal.shade600, onTap: () => _showDocument(movement['consignmentNote']))),
|
||||
],
|
||||
),
|
||||
],
|
||||
@ -525,6 +530,66 @@ class _ItemMovementItemUserScreenState extends State<ItemMovementItemUserScreen>
|
||||
);
|
||||
}
|
||||
|
||||
void _showRemark(String? remark) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Remark'),
|
||||
content: Text(remark?.isNotEmpty == true ? remark! : 'No remark available.'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Close'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showDocument(String? docPath) {
|
||||
if (docPath == null || docPath.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('No document available')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => Dialog(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
AppBar(
|
||||
title: const Text('Document'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
),
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.black,
|
||||
),
|
||||
InteractiveViewer(
|
||||
child: Image.network(
|
||||
ApiService.baseUrl + docPath,
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (ctx, err, stack) => const Padding(
|
||||
padding: EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(Icons.broken_image, size: 50, color: Colors.grey),
|
||||
Text('Failed to load image'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDate(String? dateStr) {
|
||||
if (dateStr == null || dateStr.isEmpty) return '';
|
||||
try {
|
||||
@ -696,12 +761,12 @@ class _ItemMovementItemUserScreenState extends State<ItemMovementItemUserScreen>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _actionButton(String text, {bool isPrimary = false}) {
|
||||
Widget _actionButton(String text, {bool isPrimary = false, Color? color, VoidCallback? onTap}) {
|
||||
return ElevatedButton(
|
||||
onPressed: () {},
|
||||
onPressed: onTap,
|
||||
style: isPrimary
|
||||
? ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue.shade600,
|
||||
backgroundColor: color ?? Colors.blue.shade600,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
|
||||
@ -527,7 +527,13 @@ class _ItemMovementStationUserScreenState extends State<ItemMovementStationUserS
|
||||
|
||||
Widget _flowStep(IconData icon, String label) => Column(children: [Icon(icon, size: 28, color: Colors.blueGrey), const SizedBox(height: 4), Text(label, style: const TextStyle(fontWeight: FontWeight.bold))]);
|
||||
|
||||
Widget _kvGrid(String l1, String? v1, String l2, String? v2) => Row(children: [Expanded(child: _kv(l1, v1)), Expanded(child: _kv(l2, v2))]);
|
||||
Widget _kvGrid(String l1, String? v1, String l2, String? v2) => Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(flex: 1, child: _kv(l1, v1)),
|
||||
Expanded(flex: 1, child: _kv(l2, v2))
|
||||
],
|
||||
);
|
||||
|
||||
Widget _kv(String label, String? value) => Padding(padding: const EdgeInsets.symmetric(vertical: 4.0), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Text(label, style: TextStyle(fontSize: 12, color: Colors.grey.shade600)), const SizedBox(height: 2), Text(value ?? '-', style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600))]));
|
||||
|
||||
|
||||
@ -7,9 +7,13 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <printing/printing_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) printing_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "PrintingPlugin");
|
||||
printing_plugin_register_with_registrar(printing_registrar);
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_linux
|
||||
printing
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
@ -8,11 +8,15 @@ import Foundation
|
||||
import file_picker
|
||||
import file_selector_macos
|
||||
import mobile_scanner
|
||||
import path_provider_foundation
|
||||
import printing
|
||||
import shared_preferences_foundation
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
}
|
||||
|
||||
112
pubspec.lock
112
pubspec.lock
@ -1,6 +1,14 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.7"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -17,6 +25,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
barcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: barcode
|
||||
sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.9"
|
||||
bidi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bidi
|
||||
sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.13"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -57,6 +81,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.5"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
csslib:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -184,6 +216,14 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
google_fonts:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: google_fonts
|
||||
sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.3"
|
||||
html:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -208,6 +248,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.4"
|
||||
image_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -360,6 +408,38 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_parsing
|
||||
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.22"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -384,6 +464,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
pdf:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: pdf
|
||||
sha256: "28eacad99bffcce2e05bba24e50153890ad0255294f4dd78a17075a2ba5c8416"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.11.3"
|
||||
pdf_widget_wrapper:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pdf_widget_wrapper
|
||||
sha256: c930860d987213a3d58c7ec3b7ecf8085c3897f773e8dc23da9cae60a5d6d0f5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -408,6 +504,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
posix:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: posix
|
||||
sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.3"
|
||||
printing:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: printing
|
||||
sha256: "482cd5a5196008f984bb43ed0e47cbfdca7373490b62f3b27b3299275bf22a93"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.14.2"
|
||||
qr:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@ -30,6 +30,7 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
google_fonts: ^6.3.3
|
||||
flutter_slidable: 3.1.2
|
||||
qr_flutter: ^4.1.0
|
||||
mobile_scanner: ^6.0.2
|
||||
@ -44,6 +45,8 @@ dependencies:
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.8
|
||||
shared_preferences: ^2.5.3
|
||||
pdf: ^3.11.3
|
||||
printing: ^5.14.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@ -7,8 +7,11 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <printing/printing_plugin.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
PrintingPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PrintingPlugin"));
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_windows
|
||||
printing
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
Loading…
Reference in New Issue
Block a user