Commit 6d1deaf2 authored by Mohit Kumar's avatar Mohit Kumar
Browse files

AttendanceList

RewardList
TourExpenses
Implementation
parent 55280429
This diff is collapsed.
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:geolocator/geolocator.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../Utils/app_colors.dart';
import '../../Utils/dropdownTheme.dart';
class AddLiveAttendanceScreen extends StatefulWidget {
const AddLiveAttendanceScreen({Key? key}) : super(key: key);
@override
State<AddLiveAttendanceScreen> createState() =>
_AddLiveAttendanceScreenState();
}
class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
String? selectedType;
Dropdowntheme ddtheme = Dropdowntheme();
final TextEditingController locationController = TextEditingController();
final TextEditingController descriptionController = TextEditingController();
final List<String> types = ["Check In", "Check Out"];
final ImagePicker picker = ImagePicker();
XFile? proofFile; // store selected proof
// computed labels
String get locationHeading =>
selectedType == null ? "Location" : "$selectedType Location";
String get descriptionHeading =>
selectedType == null ? "Description" : "$selectedType Description";
String get proofButtonText =>
selectedType == null ? "Attach Proof" : "Attach $selectedType Proof";
bool get isSubmitEnabled =>
selectedType != null &&
locationController.text.trim().isNotEmpty &&
proofFile != null; // proof is required
@override
void initState() {
super.initState();
locationController.addListener(() {
setState(() {});
});
}
@override
void dispose() {
locationController.dispose();
descriptionController.dispose();
super.dispose();
}
void _showPicker(BuildContext context) {
showModalBottomSheet(
context: context,
builder: (BuildContext bc) {
return SafeArea(
child: Wrap(
children: <Widget>[
ListTile(
leading: const Icon(Icons.photo_camera),
title: const Text('Camera'),
onTap: () async {
Navigator.of(context).pop();
final XFile? image =
await picker.pickImage(source: ImageSource.camera);
if (image != null) {
setState(() => proofFile = image);
}
},
),
ListTile(
leading: const Icon(Icons.photo_library),
title: const Text('Gallery'),
onTap: () async {
Navigator.of(context).pop();
final XFile? image =
await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
setState(() => proofFile = image);
}
},
),
],
),
);
},
);
}
Future<String> getCurrentLocation() async {
var status = await Permission.location.request();
if (status.isGranted) {
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
return "${position.latitude.toStringAsFixed(7)}, ${position.longitude.toStringAsFixed(7)}";
} else {
return "Permission denied";
}
}
/// New: submit function
void submitAttendance() {
print("==== Attendance Submitted ====");
print("Type: $selectedType");
print("Location: ${locationController.text}");
print("Description: ${descriptionController.text}");
print("Proof: ${proofFile?.path ?? 'No file'}");
print("=============================");
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: const Color(0xFEFFFFFF),
title: Row(
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
),
),
const SizedBox(width: 10),
Text(
"Add Live Attendance",
style: TextStyle(
fontSize: 18,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
),
),
],
),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Type Dropdown
const Text("Type",
style: TextStyle(
fontSize: 15,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w500)),
const SizedBox(height: 6),
Container(
padding: const EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8),
),
child: DropdownButtonHideUnderline(
child: DropdownButton2<String>(
isExpanded: true,
hint: const Text("Select Type",
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 15,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400),
),
value: selectedType,
items: types
.map((e) =>
DropdownMenuItem<String>(
value: e,
child: Text(
e,
style: TextStyle(
fontSize: 14,
),
overflow: TextOverflow.ellipsis,
)
))
.toList(),
onChanged: (val) => setState(() => selectedType = val),
// buttonStyleData: ddtheme.buttonStyleData,
iconStyleData: ddtheme.iconStyleData,
// menuItemStyleData: ddtheme.menuItemStyleData,
dropdownStyleData: ddtheme.dropdownStyleData,
),
),
),
const SizedBox(height: 16),
/// Location field
Text(locationHeading,
style: const TextStyle(
fontSize: 15,
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500
)),
const SizedBox(height: 6),
TextField(
controller: locationController,
readOnly: true,
onTap: () async {
String loc = await getCurrentLocation();
locationController.text = loc;
},
decoration: _inputDecoration("Tap to get location"),
),
const SizedBox(height: 16),
/// Description
Text(descriptionHeading,
style: const TextStyle(
fontSize: 15,
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500
)
),
const SizedBox(height: 6),
TextField(
controller: descriptionController,
maxLines: 3,
decoration: _inputDecoration("Write Description"),
),
const SizedBox(height: 20),
/// Attach Proof
SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: () => _showPicker(context),
style: OutlinedButton.styleFrom(
backgroundColor: Colors.blue.shade50,
side: BorderSide(color: Colors.blue.shade200),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
padding: const EdgeInsets.symmetric(vertical: 14),
),
child: Text(proofButtonText,
style: const TextStyle(
fontSize: 16,
color: Colors.blue,
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500
)),
),
),
/// Show proof preview
if (proofFile != null) ...[
const SizedBox(height: 10),
Row(
children: [
const Icon(Icons.check_circle, color: Colors.green),
const SizedBox(width: 8),
Expanded(
child: Text(
"Attached: ${proofFile!.name}",
overflow: TextOverflow.ellipsis)),
IconButton(
icon: const Icon(Icons.close, color: Colors.red),
onPressed: () => setState(() => proofFile = null),
),
],
)
],
const SizedBox(height: 24),
/// Submit button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: isSubmitEnabled ? submitAttendance : null,
style: ElevatedButton.styleFrom(
backgroundColor:
isSubmitEnabled ? Colors.blue : Colors.grey.shade400,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
),
child: const Text("Submit",
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w500)),
),
),
],
),
),
);
}
InputDecoration _inputDecoration(String hint) {
return InputDecoration(
hintText: hint,
hintStyle: const TextStyle(
fontSize: 14,
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400,
color: Colors.grey,
),
filled: true,
fillColor: Colors.grey.shade100,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.blue),
),
);
}
}
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:image_picker/image_picker.dart';
import '../../Utils/app_colors.dart';
import '../../Utils/dropdownTheme.dart';
class AddManualAttendanceScreen extends StatefulWidget {
const AddManualAttendanceScreen({super.key});
@override
State<AddManualAttendanceScreen> createState() =>
_AddManualAttendanceScreenState();
}
class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> {
final TextEditingController dateController = TextEditingController();
Dropdowntheme ddtheme = Dropdowntheme();
// Separate controllers for each section
final checkInTime = TextEditingController();
final checkInLocation = TextEditingController();
final checkInDescription = TextEditingController();
XFile? checkInProof;
final checkOutTime = TextEditingController();
final checkOutLocation = TextEditingController();
final checkOutDescription = TextEditingController();
XFile? checkOutProof;
final ImagePicker picker = ImagePicker();
String? selectedType;
final List<String> types = ["Check In", "Check Out", "Check In/Out"];
DateTime? _date;
bool get isSubmitEnabled {
if (selectedType == "Check In") {
return checkInLocation.text.trim().isNotEmpty;
} else if (selectedType == "Check Out") {
return checkOutLocation.text.trim().isNotEmpty;
} else if (selectedType == "Check In/Out") {
return checkInLocation.text.trim().isNotEmpty &&
checkOutLocation.text.trim().isNotEmpty;
}
return false;
}
@override
void initState() {
super.initState();
checkInLocation.addListener(() => setState(() {}));
checkOutLocation.addListener(() => setState(() {}));
}
// ===== Print all submitted values =====
void _submitForm() {
print("===== Manual Attendance Submitted =====");
print("Date: ${dateController.text}");
print("Type: $selectedType");
print("Time: ${checkInTime.text}");
print("Location: ${checkInLocation.text}");
print("Description: ${checkInDescription.text}");
print("Proof: ${checkInProof != null ? checkOutProof!.path : 'No file attached'}");
print("=======================================");
print("Date: ${dateController.text}");
print("Type: $selectedType");
print("Time: ${checkOutTime.text}");
print("Location: ${checkOutLocation.text}");
print("Description: ${checkOutDescription.text}");
print("Proof: ${checkInProof != null ? checkOutProof!.path : 'No file attached'}");
}
// ===== Pick File =====
Future<void> _pickFile(bool isCheckIn) async {
final XFile? file = await picker.pickImage(source: ImageSource.gallery);
if (file != null) {
setState(() {
if (isCheckIn) {
checkInProof = file;
} else {
checkOutProof = file;
}
});
}
}
void setDate(DateTime newDate) {
_date = newDate;
dateController.text = "${newDate.day}-${newDate.month}-${newDate.year}";
}
Future<void> _pickTime(TextEditingController controller) async {
final TimeOfDay? picked =
await showTimePicker(context: context, initialTime: TimeOfDay.now());
if (picked != null) {
controller.text = picked.format(context);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: const Color(0xFEFFFFFF),
title: Row(
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset("assets/svg/appbar_back_button.svg",
height: 25),
),
const SizedBox(width: 10),
Text("Add Manual Attendance",
style: TextStyle(
fontSize: 18,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
)),
],
),
),
body: Padding(
padding: const EdgeInsets.all(18),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Date
_buildLabel("Date"),
const SizedBox(height: 6),
TextField(
controller: dateController,
readOnly: true,
onTap: () => setDate(DateTime.now()),
decoration: _inputDecoration("Select Date")
.copyWith(suffixIcon: const Icon(Icons.calendar_today)),
),
const SizedBox(height: 16),
/// Type Dropdown
_buildLabel("Type"),
const SizedBox(height: 6),
Container(
padding: const EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8),
),
child: DropdownButtonHideUnderline(
child: DropdownButton2<String>(
isExpanded: true,
hint: const Text("Select Type"),
value: selectedType,
items: types
.map((e) => DropdownMenuItem<String>(
value: e,
child: Text(e),
))
.toList(),
onChanged: (val) => setState(() => selectedType = val),
// buttonStyleData: ddtheme.buttonStyleData,
iconStyleData: ddtheme.iconStyleData,
// menuItemStyleData: ddtheme.menuItemStyleData,
dropdownStyleData: ddtheme.dropdownStyleData,
),
),
),
const SizedBox(height: 20),
/// Conditional Sections
if (selectedType == "Check In")
_buildSection(
title: "Check In",
timeController: checkInTime,
locationController: checkInLocation,
descriptionController: checkInDescription,
proofFile: checkInProof,
onPickProof: () => _pickFile(true),
),
if (selectedType == "Check Out")
_buildSection(
title: "Check Out",
timeController: checkOutTime,
locationController: checkOutLocation,
descriptionController: checkOutDescription,
proofFile: checkOutProof,
onPickProof: () => _pickFile(false),
),
if (selectedType == "Check In/Out") ...[
_buildSection(
title: "Check In",
timeController: checkInTime,
locationController: checkInLocation,
descriptionController: checkInDescription,
proofFile: checkInProof,
onPickProof: () => _pickFile(true),
),
_buildSection(
title: "Check Out",
timeController: checkOutTime,
locationController: checkOutLocation,
descriptionController: checkOutDescription,
proofFile: checkOutProof,
onPickProof: () => _pickFile(false),
),
],
const SizedBox(height: 24),
/// Submit Button
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
backgroundColor:
isSubmitEnabled ? Colors.blue : Colors.grey.shade400,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
),
onPressed: isSubmitEnabled
? () {
_submitForm();
print("Submit pressed for $selectedType");
}
: null,
child: const Text("Submit",
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w500,
)),
),
),
],
),
),
),
);
}
/// Reusable Section
Widget _buildSection({
required String title,
required TextEditingController timeController,
required TextEditingController locationController,
required TextEditingController descriptionController,
required XFile? proofFile,
required VoidCallback onPickProof,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLabel("$title Time"),
const SizedBox(height: 6),
TextField(
controller: timeController,
readOnly: true,
onTap: () => _pickTime(timeController),
decoration: _inputDecoration("Select Time")
.copyWith(suffixIcon: const Icon(Icons.access_time)),
),
const SizedBox(height: 16),
_buildLabel("$title Location"),
const SizedBox(height: 6),
TextField(
controller: locationController,
decoration: _inputDecoration("Enter Location"),
),
const SizedBox(height: 16),
_buildLabel("$title Description"),
const SizedBox(height: 6),
TextField(
controller: descriptionController,
maxLines: 3,
decoration: _inputDecoration("Write Description"),
),
const SizedBox(height: 18),
/// Proof
SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: onPickProof,
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
backgroundColor: Colors.blue.shade50,
side: BorderSide(color: Colors.blue.shade200),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
),
child: Text("Attach $title Proof",
style: const TextStyle(
fontSize: 16,
color: Colors.blue,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w500)),
),
),
// Show proof preview
if (proofFile != null) ...[
const SizedBox(height: 10),
Row(
children: [
const Icon(Icons.check_circle, color: Colors.green),
const SizedBox(width: 8),
Expanded(
child: Text(
"Attached: ${proofFile!.name}",
overflow: TextOverflow.ellipsis)),
IconButton(
icon: const Icon(Icons.close, color: Colors.red),
onPressed: () => setState(() => proofFile = null),
),
],
)
],
const SizedBox(height: 24),
],
);
}
Widget _buildLabel(String text) => Text(text,
style: const TextStyle(
fontSize: 15,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w500));
InputDecoration _inputDecoration(String hint) {
return InputDecoration(
hintText: hint,
hintStyle: const TextStyle(
fontSize: 14,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
color: Colors.grey),
filled: true,
fillColor: Colors.grey.shade100,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.blue)),
);
}
}
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
import '../../Utils/app_colors.dart';
import '../../Utils/commonServices.dart';
import '../../Utils/commonWidgets.dart';
import '../../Utils/dropdownTheme.dart';
class AddBillScreen extends StatefulWidget {
final String pageTitleName;
const AddBillScreen({super.key, required this.pageTitleName});
@override
State<AddBillScreen> createState() => _AddBillScreenState();
}
class _AddBillScreenState extends State<AddBillScreen> {
Dropdowntheme ddtheme = Dropdowntheme();
List<FocusNode> focusNodes = List.generate(8, (index) => FocusNode());
Map _source = {ConnectivityResult.mobile: true};
final MyConnectivity _connectivity = MyConnectivity.instance;
TextEditingController placeController = TextEditingController();
TextEditingController dateController = TextEditingController();
TextEditingController noteController = TextEditingController();
String? selectedDAAmount;
String? selectedTourType;
List<Map<String, String>> travelExpenses = [
{"title": "Bike", "amount": "1800", "icon": "assets/svg/ic_bike.svg"},
{"title": "Taxi", "amount": "300", "icon": "assets/svg/ic_taxi.svg"},
];
List<Map<String, String>> hotelExpenses = [
{"title": "Hotel Sharada", "amount": "1800", "icon": "assets/svg/ic_hotel.svg"},
{"title": "Hotel Nikitan", "amount": "1800", "icon": "assets/svg/ic_hotel.svg"},
];
List<Map<String, String>> otherExpenses = [
{"title": "Book", "amount": "1800", "icon": "assets/svg/ic_book.svg"},
];
@override
void initState() {
super.initState();
_connectivity.initialise();
_connectivity.myStream.listen((source) {
setState(() => _source = source);
});
}
@override
void dispose() {
placeController.dispose();
dateController.dispose();
noteController.dispose();
focusNodes.map((e) => e.dispose());
_connectivity.disposeStream();
super.dispose();
}
Future<bool> _onBackPressed(BuildContext context) async {
Navigator.pop(context, true);
return true;
}
@override
Widget build(BuildContext context) {
switch (_source.keys.toList()[0]) {
case ConnectivityResult.mobile:
case ConnectivityResult.wifi:
connection = 'Online';
break;
case ConnectivityResult.none:
default:
connection = 'Offline';
}
return (connection == "Online")
? Platform.isAndroid
? WillPopScope(
onWillPop: () => _onBackPressed(context),
child: SafeArea(
top: false,
bottom: true,
child: _scaffold(context),
),
)
: _scaffold(context)
: NoNetwork(context);
}
Widget _scaffold(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: AppColors.scaffold_bg_color,
appBar: appbarNew(context, widget.pageTitleName, 0xFFFFFFFF),
body: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
margin: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Place of Visit
TextWidget(context, "Place of Visit"),
textFieldNew(context, placeController, "Enter Place"),
/// DA Amount
TextWidget(context, "DA Amount"),
dropDownField(context, "Select DA Amount", ["100", "200", "300"], selectedDAAmount, (val) {
setState(() => selectedDAAmount = val);
}),
/// Tour Type
TextWidget(context, "Tour Type"),
dropDownField(context, "Select Tour", ["Business", "Personal"], selectedTourType, (val) {
setState(() => selectedTourType = val);
}),
/// Tour Date
TextWidget(context, "Tour Date"),
GestureDetector(
onTap: () async {
DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2022),
lastDate: DateTime(2100),
);
if (picked != null) {
dateController.text = DateFormat("dd MMM yyyy").format(picked);
setState(() {});
}
},
child: textFieldNew(context, dateController, "Enter Date", enabled: false),
),
/// Note
TextWidget(context, "Note"),
textFieldNew(context, noteController, "Write Note", maxLines: 3),
const SizedBox(height: 16),
/// Travel Expenses
sectionHeader("Travel Expenses", onAddTap: () {
// TODO: Add Travel Expense
}),
expenseList(travelExpenses),
/// Hotel Expenses
sectionHeader("Hotel Expenses", onAddTap: () {
// TODO: Add Hotel Expense
}),
expenseList(hotelExpenses),
/// Other Expenses
sectionHeader("Other Expenses", onAddTap: () {
// TODO: Add Other Expense
}),
expenseList(otherExpenses),
const SizedBox(height: 80),
],
),
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
bottomNavigationBar: InkResponse(
onTap: () {
// TODO: Submit API Call
},
child: Container(
height: 45,
alignment: Alignment.center,
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 15),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: AppColors.app_blue,
borderRadius: BorderRadius.circular(15),
),
child: const Text(
"Submit",
style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
color: Colors.white,
),
),
),
),
);
}
/// --- Custom Widgets Below ---
Widget textFieldNew(BuildContext context, TextEditingController controller, String hint,
{bool enabled = true, int maxLines = 1}) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: AppColors.text_field_color,
borderRadius: BorderRadius.circular(14),
),
child: TextFormField(
controller: controller,
enabled: enabled,
maxLines: maxLines,
decoration: _inputDecoration(hint)
),
);
}
Widget dropDownField(BuildContext context, String hint, List<String> items, String? value, Function(String?) onChanged) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: AppColors.text_field_color,
borderRadius: BorderRadius.circular(14),
),
child: DropdownButtonHideUnderline(
child: DropdownButton2<String>(
isExpanded: true,
hint: Text(hint, style: const TextStyle(fontSize: 14, color: Colors.grey)),
value: value,
items: items.map((e) => DropdownMenuItem(value: e, child: Text(e))).toList(),
onChanged: onChanged,
iconStyleData: ddtheme.iconStyleData,
// menuItemStyleData: ddtheme.menuItemStyleData,
dropdownStyleData: ddtheme.dropdownStyleData,
),
),
);
}
Widget sectionHeader(String title, {VoidCallback? onAddTap}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
const SizedBox(height: 6),
Container(
height: 45,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade400, width: 0.7, style: BorderStyle.solid),
borderRadius: BorderRadius.circular(12),
),
child: InkWell(
onTap: onAddTap,
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(Icons.add, color: Colors.blue),
SizedBox(width: 6),
Text("Add Expenses", style: TextStyle(color: Colors.blue, fontSize: 14)),
],
),
),
),
),
const SizedBox(height: 10),
],
);
}
Widget expenseList(List<Map<String, String>> items) {
return Container(
height: 84,
margin: const EdgeInsets.only(bottom: 12),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: items.length,
itemBuilder: (context, index) {
final exp = items[index];
return Container(
width: 120,
margin: const EdgeInsets.only(right: 10),
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: const Color(0xFFE6F6FF),
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(exp["icon"] ?? "assets/svg/ic_default.svg", height: 22),
const SizedBox(height: 6),
Text(exp["title"] ?? "-", style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500)),
Text("₹${exp["amount"]}", style: const TextStyle(fontSize: 12, color: Colors.blue)),
],
),
);
},
),
);
}
InputDecoration _inputDecoration(String hint) {
return InputDecoration(
hintText: hint,
hintStyle: TextStyle(
fontSize: 14,
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400,
color: Color(0xFFB4BEC0),
),
filled: true,
fillColor: Colors.grey.shade100,
enabledBorder: InputBorder.none,
disabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
);
}
}
This diff is collapsed.
This diff is collapsed.
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:generp/screens/hrm/RewardListScreen.dart';
import '../../Utils/app_colors.dart';
import 'AttendanceRequestDetail.dart';
import 'LeaveApplicationScreen.dart';
import 'TourExpensesListScreen.dart';
import 'attendancelist.dart';
class HrmdashboardScreen extends StatefulWidget {
const HrmdashboardScreen({super.key});
@override
State<HrmdashboardScreen> createState() => _HrmdashboardScreenState();
}
class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: const Color(0xFFCEEDFF),
// elevation: 2.0,
title: SizedBox(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
),
),
const SizedBox(width: 10),
InkResponse(
onTap: () => Navigator.pop(context, true),
child: Text(
"HRM",
style: TextStyle(
fontSize: 18,
height: 1.1,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
),
),
),
],
),
),
),
backgroundColor: Color(0xffF6F6F8),
body: SingleChildScrollView(
child: Column(
children: [
/// Background elements
Stack(
children: [
Container(
width: double.infinity,
height: 490,
color: const Color(0xffF6F6F8),
),
Container(
width: double.infinity,
height: 490,
padding: const EdgeInsets.only(top: 1, bottom: 30),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [
Color(0xFFCEEDFF),
Color(0xFFf9f9fb),
Color(0xffF6F6F8)
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
),
Container(
width: double.infinity,
padding: const EdgeInsets.only(top: 1, bottom: 30),
child: Image.asset(
"assets/images/vector.png",
height: 230,
width: double.infinity,
fit: BoxFit.fitWidth,
),
),
Column(
children: [
/// Top Section with Gradient
Container(
width: double.infinity,
padding: const EdgeInsets.only(top: 60, bottom: 30),
child: Column(
children: [
/// Illustration
SvgPicture.asset(
"assets/images/capa.svg",
height: 146,
width: 400,
fit: BoxFit.contain,
),
const SizedBox(height: 32),
/// Organization Structure Button
Container(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 8),
decoration: BoxDecoration(
border: Border.all(
color: const Color(0xFF1487C9), // border color
width: 1.2, // thickness of the border
),
color: const Color(0xffEDF8FF),
borderRadius: BorderRadius.circular(30),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(
"assets/svg/hrm/groupIc.svg",
height: 29,
width: 29,
fit: BoxFit.contain,
),
const SizedBox(width: 7),
const Text(
"Organization Structure",
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, fontStyle: FontStyle.normal, fontFamily: "Plus Jakarta Sans"),
),
const Icon(Icons.chevron_right, color: Colors.black54),
],
),
),
],
),
),
/// Bottom Grid Section
Padding(
padding: const EdgeInsets.all(15),
child: GridView.count(
crossAxisCount: 2, // items per row
crossAxisSpacing: 8.5,
mainAxisSpacing: 16,
childAspectRatio: 2.0, // tiles height
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: [
_buildTile(
label: "Attendance List",
subtitle: "Real-time request",
assetIcon: "assets/svg/hrm/attendanceList.svg",
txtColor: const Color(0xff1487C9),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const Attendancelist(),
),
);
},
),
_buildTile(
label: "Leave Application",
subtitle: "Apply & Track",
assetIcon: "assets/svg/hrm/leaveApplication.svg",
txtColor: const Color(0xff1487C9),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const LeaveApplicationScreen(),
),
);
},
),
_buildTile(
label: "Rewards List",
subtitle: "Track earned rewards",
assetIcon: "assets/svg/hrm/rewardList.svg",
txtColor: const Color(0xff1487C9),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const RewardListScreen(),
),
);
},
),
_buildTile(
label: "Tour Expenses",
subtitle: "Submit and manage claims",
assetIcon: "assets/svg/hrm/tourExp.svg",
txtColor: const Color(0xff1487C9),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const TourExpensesListScreen(),
),
);
},
),
],
),
),
],
),
],
),
],
),
),
);
}
/// Reusable Tile Widget (Row style)
Widget _buildTile({
required String label,
required String subtitle,
required String assetIcon, // SVG/PNG asset instead of IconData
required Color txtColor,
VoidCallback? onTap,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(20),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
child: Row(
children: [
/// Left side text
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 15,
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500,
color: txtColor,
),
),
const SizedBox(height: 5),
Text(
subtitle,
style: const TextStyle(
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
fontSize: 12,
height: 1.4,
color: Color(0xff818181),
),
),
],
),
),
/// Right side icon (SVG/PNG)
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: Container(
height: 48,
width: 48,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFFEDF8FF), // icon bg
),
child: Center(
child: SvgPicture.asset(
height: 28,
width: 28,
assetIcon,
fit: BoxFit.contain,
),
),
),
),
),
],
),
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class LeaveApplicationScreen extends StatefulWidget {
const LeaveApplicationScreen({Key? key}) : super(key: key);
@override
State<LeaveApplicationScreen> createState() => _LeaveApplicationScreenState();
}
class _LeaveApplicationScreenState extends State<LeaveApplicationScreen> {
final TextEditingController _reasonController = TextEditingController();
DateTime? _startDate;
DateTime? _endDate;
Future<void> _pickDate({required bool isStart}) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2020),
lastDate: DateTime(2100),
);
if (picked != null) {
setState(() {
if (isStart) {
_startDate = picked;
} else {
_endDate = picked;
}
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.white,
title: Row(
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
),
),
const SizedBox(width: 10),
const Text(
"Leave Application",
style: TextStyle(
fontSize: 18,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
],
),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Align(
alignment: Alignment.topRight,
child: Text(
"Dummy Screen !",
style: TextStyle(
fontSize: 10,
height: 1,
fontWeight: FontWeight.bold,
),
),
),
const Text(
"Apply for Leave",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
TextField(
controller: _reasonController,
decoration: const InputDecoration(
labelText: "Reason for Leave",
border: OutlineInputBorder(),
),
maxLines: 3,
),
const SizedBox(height: 20),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () => _pickDate(isStart: true),
child: Text(_startDate == null
? "Select Start Date"
: "Start: ${_startDate!.toLocal()}".split(' ')[0]),
),
),
const SizedBox(width: 10),
Expanded(
child: ElevatedButton(
onPressed: () => _pickDate(isStart: false),
child: Text(_endDate == null
? "Select End Date"
: "End: ${_endDate!.toLocal()}".split(' ')[0]),
),
),
],
),
const Spacer(),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
// Handle submission logic here
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Leave application submitted")),
);
},
child: const Text("Submit Application"),
),
),
],
),
),
);
}
}
This diff is collapsed.
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import '../../Utils/app_colors.dart';
class RewardSearchScreen extends StatefulWidget {
const RewardSearchScreen({super.key});
@override
State<RewardSearchScreen> createState() => _RewardSearchScreenState();
}
class _RewardSearchScreenState extends State<RewardSearchScreen> {
final TextEditingController _searchController = TextEditingController();
// Dummy data
final List<String> accounts = [
"Siddha Bank",
"Siddharth Shivam",
"Sidheshwar Temple",
"Axis Bank",
"SBI",
];
final List<String> inquiries = [
"Inquiry Siddha Bank",
"Inquiry Sidharth",
"Inquiry Sidheshwar",
"Customer SBI",
];
final List<String> leads = [
"Lead Siddha Bank",
"Lead Shivam",
"Lead Sidheshwar",
"Lead HDFC",
];
// Filtered data
String query = "";
@override
Widget build(BuildContext context) {
final filteredAccounts =
accounts.where((e) => e.toLowerCase().contains(query.toLowerCase())).toList();
final filteredInquiries =
inquiries.where((e) => e.toLowerCase().contains(query.toLowerCase())).toList();
final filteredLeads =
leads.where((e) => e.toLowerCase().contains(query.toLowerCase())).toList();
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: const Color(0xFFFFFFFF),
title: Row(
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
),
),
const SizedBox(width: 10),
const Text(
"Search",
style: TextStyle(
fontSize: 18,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
],
),
),
backgroundColor: Color(0xFFF6F6F8),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Search Field
TextField(
controller: _searchController,
onChanged: (value) {
setState(() {
query = value;
});
},
decoration: InputDecoration(
hintText: "Search",
prefixIcon: const Icon(Icons.search, color: Colors.black54),
filled: true,
fillColor: const Color(0xffFFFFFF),
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
),
),
const SizedBox(height: 20),
if (query.isNotEmpty)
Text(
'Result for “$query”',
style: const TextStyle(
fontSize: 18,
color: Color(0xff1487C9),
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 20),
if (query.isNotEmpty && filteredAccounts.isNotEmpty)
_buildSection("Accounts", filteredAccounts),
if (query.isNotEmpty && filteredInquiries.isNotEmpty)
_buildSection("Inquiries", filteredInquiries),
if (query.isNotEmpty && filteredLeads.isNotEmpty)
_buildSection("Leads", filteredLeads),
if (query.isNotEmpty &&
filteredAccounts.isEmpty &&
filteredInquiries.isEmpty &&
filteredLeads.isEmpty)
const Center(
child: Text(
"No results found",
style: TextStyle(fontSize: 14, color: Colors.grey),
),
),
],
),
),
);
}
/// Reusable Section Widget
Widget _buildSection(String title, List<String> items) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
fontFamily: "Plus Jakarta Sans",
color: Color(0xff2D2D2D),
),
),
const SizedBox(height: 10),
Column(
children: items
.map((e) => Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 5,
offset: const Offset(0, 3),
)
],
),
child: Row(
children: [
Container(
height: 32,
width: 32,
decoration: BoxDecoration(
color: const Color(0xffE8F3FF),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(Icons.search,
color: Color(0xff0066FF), size: 20),
),
const SizedBox(width: 10),
Expanded(
child: Text(
e,
style: const TextStyle(
fontSize: 14,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
color: Color(0xff2D2D2D),
),
overflow: TextOverflow.ellipsis,
),
),
],
),
))
.toList(),
)
],
);
}
}
This diff is collapsed.
This diff is collapsed.
......@@ -56,3 +56,10 @@ export 'package:generp/Notifiers/crmProvider/addProspectLeadsProvider.dart';
export 'package:generp/Notifiers/crmProvider/followUpUpdateProvider.dart';
export 'package:generp/Notifiers/crmProvider/appointmentCalendarProvider.dart';
export 'package:generp/Notifiers/crmProvider/addNewLeadsandProspectsProvider.dart';
export 'package:generp/Notifiers/hrmProvider/attendanceListProvider.dart';
export 'package:generp/Notifiers/hrmProvider/AttendanceDetailsProvider.dart';
export 'package:generp/Notifiers/hrmProvider/tourExpensesProvider.dart';
export 'package:generp/Notifiers/hrmProvider/tourExpensesDetailsProvider.dart';
export 'package:generp/Notifiers/hrmProvider/rewardListProvider.dart';
......@@ -54,3 +54,5 @@ export 'package:generp/screens/crm/QuotationDetails.dart';
export 'package:generp/screens/crm/contactDetails.dart';
export 'package:generp/screens/crm/editAccountDetails.dart';
export 'package:generp/screens/crm/productDetails.dart';
// hrm screen export
export 'package:generp/screens/hrm/Attendancelist.dart';
This diff is collapsed.
......@@ -178,6 +178,18 @@ const crmDashboardFollowUpUrl = "${baseUrl_test}crm_dashboard_followup_list";
const crmDashboardQuotationsUrl = "${baseUrl_test}crm_dashboard_quotations_list";
///HRM
//Attendance
const AttendanceRequestListUrl ="${baseUrl_test}attendance_request_list";
const AttendanceRequestDetailsUrl ="${baseUrl_test}attendance_request_details";
const AddAttendanceRequestUrl ="${baseUrl_test}add_attendance_request";
// reward list
const RewardListUrl ="${baseUrl_test}hrm_emp_self_rewards";
// Tour Expenses hrm_emp_self_rewards
const TourExpensesListUrl ="${baseUrl_test}tour_bill_list";
const TourExpensesDetailsUrl ="${baseUrl_test}tour_bill_details";
......
......@@ -126,6 +126,7 @@ flutter:
- assets/svg/service/
- assets/svg/crm/
- assets/svg/order/
- assets/svg/hrm/
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment