marco.pms.mobileapp/lib/view/service_project/service_project_details_screen.dart

406 lines
13 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:marco/controller/project_controller.dart';
import 'package:marco/controller/service_project/service_project_details_screen_controller.dart';
import 'package:marco/helpers/utils/launcher_utils.dart';
import 'package:marco/helpers/widgets/my_spacing.dart';
import 'package:marco/helpers/widgets/my_text.dart';
class ServiceProjectDetailsScreen extends StatefulWidget {
final String projectId;
const ServiceProjectDetailsScreen({super.key, required this.projectId});
@override
State<ServiceProjectDetailsScreen> createState() =>
_ServiceProjectDetailsScreenState();
}
class _ServiceProjectDetailsScreenState
extends State<ServiceProjectDetailsScreen>
with SingleTickerProviderStateMixin {
late TabController _tabController;
final ServiceProjectDetailsController controller =
Get.put(ServiceProjectDetailsController());
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
controller.setProjectId(widget.projectId);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
// ---------------- Helper Widgets ----------------
Widget _buildDetailRow({
required IconData icon,
required String label,
required String value,
VoidCallback? onTap,
VoidCallback? onLongPress,
bool isActionable = false,
}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: InkWell(
onTap: isActionable && value != 'NA' ? onTap : null,
onLongPress: isActionable && value != 'NA' ? onLongPress : null,
borderRadius: BorderRadius.circular(5),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.redAccent.withOpacity(0.1),
borderRadius: BorderRadius.circular(5),
),
child: Icon(icon, size: 20, color: Colors.redAccent),
),
MySpacing.width(16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
fontWeight: FontWeight.w500,
),
),
MySpacing.height(4),
Text(
value,
style: TextStyle(
fontSize: 15,
color: isActionable && value != 'NA'
? Colors.redAccent
: Colors.black87,
fontWeight: FontWeight.w500,
decoration: isActionable && value != 'NA'
? TextDecoration.underline
: TextDecoration.none,
),
),
],
),
),
if (isActionable && value != 'NA')
Icon(Icons.chevron_right, color: Colors.grey[400], size: 20),
],
),
),
);
}
Widget _buildSectionCard({
required String title,
required IconData titleIcon,
required List<Widget> children,
}) {
return Card(
elevation: 2,
shadowColor: Colors.black12,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(titleIcon, size: 20, color: Colors.redAccent),
MySpacing.width(8),
Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87),
),
],
),
MySpacing.height(8),
const Divider(),
...children,
],
),
),
);
}
String _formatDate(DateTime? date) {
if (date == null) return 'NA';
try {
return DateFormat('d/M/yyyy').format(date);
} catch (_) {
return 'NA';
}
}
Widget _buildProfileTab() {
final project = controller.projectDetail.value;
if (project == null) return const Center(child: Text("No project data"));
return Padding(
padding: MySpacing.all(12),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Header
Card(
elevation: 2,
shadowColor: Colors.black12,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
const Icon(Icons.work_outline,
size: 45, color: Colors.redAccent),
MySpacing.width(16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.titleMedium(project.name, fontWeight: 700),
MySpacing.height(6),
MyText.bodySmall(
project.client?.name ?? 'N/A', fontWeight: 500),
],
),
),
],
),
),
),
MySpacing.height(16),
// Project Information
_buildSectionCard(
title: 'Project Information',
titleIcon: Icons.info_outline,
children: [
_buildDetailRow(
icon: Icons.calendar_today_outlined,
label: 'Assigned Date',
value: _formatDate(project.assignedDate),
),
_buildDetailRow(
icon: Icons.location_on_outlined,
label: 'Address',
value: project.address,
),
_buildDetailRow(
icon: Icons.people_outline,
label: 'Contact Name',
value: project.contactName,
),
_buildDetailRow(
icon: Icons.phone_outlined,
label: 'Contact Phone',
value: project.contactPhone,
isActionable: true,
onTap: () =>
LauncherUtils.launchPhone(project.contactPhone),
onLongPress: () => LauncherUtils.copyToClipboard(
project.contactPhone,
typeLabel: 'Phone'),
),
_buildDetailRow(
icon: Icons.email_outlined,
label: 'Contact Email',
value: project.contactEmail,
isActionable: true,
onTap: () =>
LauncherUtils.launchEmail(project.contactEmail),
onLongPress: () => LauncherUtils.copyToClipboard(
project.contactEmail,
typeLabel: 'Email'),
),
],
),
MySpacing.height(12),
// Status
if (project.status != null)
_buildSectionCard(
title: 'Status',
titleIcon: Icons.flag_outlined,
children: [
_buildDetailRow(
icon: Icons.info_outline,
label: 'Status',
value: project.status!.status,
),
],
),
// Services
if (project.services != null && project.services!.isNotEmpty)
_buildSectionCard(
title: 'Services',
titleIcon: Icons.miscellaneous_services_outlined,
children: project.services!.map((service) {
return _buildDetailRow(
icon: Icons.build_outlined,
label: service.name,
value: service.description ?? '-',
);
}).toList(),
),
MySpacing.height(12),
// Client Section
if (project.client != null)
_buildSectionCard(
title: 'Client Information',
titleIcon: Icons.business_outlined,
children: [
_buildDetailRow(
icon: Icons.person_outline,
label: 'Client Name',
value: project.client!.name,
),
_buildDetailRow(
icon: Icons.phone_outlined,
label: 'Client Phone',
value: project.client!.contactNumber ?? 'NA',
isActionable: true,
onTap: () => LauncherUtils.launchPhone(
project.client!.contactNumber ?? ''),
onLongPress: () => LauncherUtils.copyToClipboard(
project.client!.contactNumber ?? '',
typeLabel: 'Phone'),
),
],
),
MySpacing.height(40),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
appBar: PreferredSize(
preferredSize: const Size.fromHeight(72),
child: AppBar(
backgroundColor: const Color(0xFFF5F5F5),
elevation: 0.5,
automaticallyImplyLeading: false,
titleSpacing: 0,
title: Padding(
padding: MySpacing.xy(16, 0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton(
icon: const Icon(Icons.arrow_back_ios_new,
color: Colors.black, size: 20),
onPressed: () => Get.toNamed('/dashboard/service-projects'),
),
MySpacing.width(8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
MyText.titleLarge(
'Service Projects',
fontWeight: 700,
color: Colors.black,
),
MySpacing.height(2),
GetBuilder<ProjectController>(
builder: (projectController) {
final projectName =
projectController.selectedProject?.name ??
'Select Project';
return Row(
children: [
const Icon(Icons.work_outline,
size: 14, color: Colors.grey),
MySpacing.width(4),
Expanded(
child: MyText.bodySmall(
projectName,
fontWeight: 600,
overflow: TextOverflow.ellipsis,
color: Colors.grey[700],
),
),
],
);
},
),
],
),
),
],
),
),
),
),
body: Column(
children: [
// ---------------- TabBar ----------------
Container(
color: Colors.white,
child: TabBar(
controller: _tabController,
labelColor: Colors.black,
unselectedLabelColor: Colors.grey,
indicatorColor: Colors.red,
indicatorWeight: 3,
isScrollable: false,
tabs: const [
Tab(text: "Profile"),
Tab(text: "Jobs"),
],
),
),
// ---------------- TabBarView ----------------
Expanded(
child: Obx(() {
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
if (controller.errorMessage.value.isNotEmpty) {
return Center(child: Text(controller.errorMessage.value));
}
return TabBarView(
controller: _tabController,
children: [
// Profile Tab
_buildProfileTab(),
// Jobs Tab - empty
Container(color: Colors.white),
],
);
}),
),
],
),
);
}
}