first commit

This commit is contained in:
2026-02-07 15:57:09 +07:00
commit 157096f164
1153 changed files with 415766 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'package:mobile_pos/generated/l10n.dart' as l;
import 'package:flutter_svg/flutter_svg.dart';
Future<bool> showDeleteConfirmationDialog({
required BuildContext context,
required String itemName, // Name of the item to delete
}) async {
final _theme = Theme.of(context);
final _lang = l.S.of(context);
return await showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext dialogContext) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(15),
),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${_lang.doYouReallyWantToDeleteThis} $itemName?',
style: _theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w500,
color: Colors.black,
),
textAlign: TextAlign.center,
),
SizedBox(height: 26),
Center(child: SvgPicture.asset('assets/hrm/delete.svg')),
SizedBox(height: 26),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () {
Navigator.pop(context, false);
},
child: Text(_lang.no),
),
),
SizedBox(width: 16),
Expanded(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context, true);
},
child: Text(_lang.yes),
),
),
],
)
],
),
),
),
),
);
},
);
}

View File

@@ -0,0 +1,102 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:mobile_pos/generated/l10n.dart' as l;
import 'package:mobile_pos/constant.dart';
class FilterDropdownButton<T> extends StatefulWidget {
const FilterDropdownButton({
super.key,
this.value,
required this.items,
this.onChanged,
this.buttonDecoration,
this.hint,
this.buttonHeight = 40,
this.buttonWidth,
this.dropdownWidth,
this.icon,
this.iconSize = 24,
this.dropdownDecoration,
this.selectedItemBuilder,
});
final T? value;
final List<DropdownMenuItem<T>> items;
final ValueChanged<T?>? onChanged;
final BoxDecoration? buttonDecoration;
final Widget? hint;
final double buttonHeight;
final double? buttonWidth;
final double? dropdownWidth;
final Widget? icon;
final double iconSize;
final BoxDecoration? dropdownDecoration;
final DropdownButtonBuilder? selectedItemBuilder;
@override
State<FilterDropdownButton<T>> createState() => _FilterDropdownButtonState<T>();
}
class _FilterDropdownButtonState<T> extends State<FilterDropdownButton<T>> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isDarkMode = theme.brightness == Brightness.dark;
return DropdownButtonHideUnderline(
child: DropdownButton2<T>(
isExpanded: true,
value: widget.value,
items: widget.items,
onChanged: widget.onChanged,
selectedItemBuilder: widget.selectedItemBuilder,
hint: widget.hint ?? Text(l.S.of(context).selectOne),
buttonStyleData: ButtonStyleData(
height: widget.buttonHeight,
width: widget.buttonWidth,
padding: const EdgeInsets.symmetric(horizontal: 2),
decoration: widget.buttonDecoration ??
BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(
color: kBorderColor,
),
color: isDarkMode ? Colors.grey.shade900 : Colors.white,
),
elevation: 0,
),
iconStyleData: IconStyleData(
icon: widget.icon ??
const Icon(
Icons.keyboard_arrow_down,
color: kNeutral800,
),
iconSize: widget.iconSize,
iconEnabledColor: theme.iconTheme.color,
iconDisabledColor: Colors.grey,
),
dropdownStyleData: DropdownStyleData(
maxHeight: 250,
width: widget.dropdownWidth,
padding: null,
decoration: widget.dropdownDecoration ??
BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: isDarkMode ? Colors.grey.shade900 : Colors.white,
),
elevation: 4,
// offset: const Offset(0, 0),
scrollbarTheme: ScrollbarThemeData(
radius: const Radius.circular(40),
thickness: WidgetStateProperty.all<double>(6),
thumbVisibility: WidgetStateProperty.all<bool>(true),
),
),
menuItemStyleData: const MenuItemStyleData(
height: 40,
padding: EdgeInsets.symmetric(horizontal: 12),
),
),
);
}
}

View File

@@ -0,0 +1,80 @@
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:mobile_pos/generated/l10n.dart' as l;
import '../../../constant.dart';
class GlobalSearchAppBar extends StatelessWidget implements PreferredSizeWidget {
final bool isSearch;
final VoidCallback onSearchToggle;
final String title;
final TextEditingController? controller;
final ValueChanged<String>? onChanged;
final PopupMenuItemSelected<dynamic>? onManuSelect;
final List<PopupMenuItem>? popupMenuButtons;
const GlobalSearchAppBar({
super.key,
required this.isSearch,
required this.onSearchToggle,
required this.title,
this.controller,
this.onChanged,
this.popupMenuButtons,
this.onManuSelect,
});
@override
Widget build(BuildContext context) {
return AppBar(
title: isSearch
? TextField(
autofocus: true,
controller: controller,
onChanged: onChanged,
decoration: InputDecoration(
hintText: '${l.S.of(context).searchH}...',
border: InputBorder.none,
suffixIcon: Icon(
FeatherIcons.search,
// size: 18,
color: kNeutral800,
),
),
)
: Text(title),
centerTitle: true,
automaticallyImplyLeading: !isSearch,
actions: [
IconButton(
style: ButtonStyle(
overlayColor: WidgetStatePropertyAll(Colors.transparent),
),
visualDensity: const VisualDensity(horizontal: -4),
padding: EdgeInsets.only(right: 16),
onPressed: onSearchToggle,
icon: Icon(
isSearch ? Icons.close : FeatherIcons.search,
color: isSearch ? kMainColor : kNeutral800,
size: isSearch ? null : 22,
),
),
if (popupMenuButtons != null)
PopupMenuButton(
onSelected: onManuSelect,
itemBuilder: (context) => popupMenuButtons!,
icon: const Icon(Icons.more_vert),
)
],
bottom: PreferredSize(
preferredSize: Size.fromHeight(1),
child: Divider(
height: 2,
color: kBackgroundColor,
)),
);
}
@override
Size get preferredSize => Size.fromHeight(isSearch ? 65 : kToolbarHeight);
}

View File

@@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
Widget labelSpan({required String title, required BuildContext context}) {
final _theme = Theme.of(context);
return Text.rich(
TextSpan(text: title, children: [
TextSpan(
text: '*',
style: TextStyle(color: Colors.red),
)
]),
style: _theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w500,
),
);
}

View File

@@ -0,0 +1,138 @@
import 'package:flutter/material.dart';
import 'package:mobile_pos/generated/l10n.dart' as l;
import '../../../Const/api_config.dart';
import '../../../constant.dart';
void viewModalSheet({
required BuildContext context,
required Map<String, String> item,
String? description,
bool? showImage,
String? image,
String? descriptionTitle,
}) {
final _theme = Theme.of(context);
showModalBottomSheet(
context: context,
isScrollControlled: true,
useSafeArea: true,
isDismissible: false,
builder: (BuildContext context) {
return SingleChildScrollView(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsetsDirectional.only(start: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
l.S.of(context).viewDetails,
style: _theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 18,
),
),
IconButton(
onPressed: () => Navigator.pop(context),
icon: Icon(Icons.close, size: 18),
)
],
),
),
Divider(color: kBorderColor, height: 1),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (showImage == true) ...[
SizedBox(height: 15),
image != null
? Center(
child: Container(
height: 120,
width: 120,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
fit: BoxFit.cover,
image: NetworkImage('${APIConfig.domain}$image'),
),
),
),
)
: Center(
child: Image.asset(
height: 120,
width: 120,
fit: BoxFit.cover,
'assets/hrm/image_icon.jpg',
),
),
SizedBox(height: 21),
],
Column(
children: item.entries.map((entry) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
fit: FlexFit.tight,
flex: 2,
child: Text(
'${entry.key} ',
style: _theme.textTheme.bodyLarge?.copyWith(
color: kNeutral800,
fontWeight: FontWeight.w500,
),
),
),
SizedBox(width: 8),
Flexible(
fit: FlexFit.tight,
flex: 4,
child: Text(
': ${entry.value}',
style: _theme.textTheme.bodyLarge,
),
),
],
),
);
}).toList(),
),
if (description != null) ...[
SizedBox(height: 16),
Text.rich(
TextSpan(
text: "${descriptionTitle ?? l.S.of(context).description}\n",
style: _theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w500,
),
children: [
TextSpan(
text: description,
style: _theme.textTheme.bodyLarge?.copyWith(
color: kNeutral800,
),
)
]),
),
],
],
),
),
],
),
);
},
);
}

View File

@@ -0,0 +1,86 @@
// import 'package:flutter/material.dart';
//
// Future<void> setTime(TextEditingController controller, BuildContext context) async {
// TimeOfDay initialTime = TimeOfDay.now();
//
// if (controller.text.isNotEmpty) {
// final timeParts = controller.text.split(' ');
// final time = timeParts[0].split(':');
// final period = timeParts[1];
//
// int hour = int.parse(time[0]);
// if (period == 'PM' && hour != 12) hour += 12;
// if (period == 'AM' && hour == 12) hour = 0;
//
// initialTime = TimeOfDay(hour: hour, minute: int.parse(time[1]));
// }
//
// final TimeOfDay? picked = await showTimePicker(
// context: context,
// initialTime: initialTime,
// builder: (context, child) => MediaQuery(
// data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: false),
// child: child!,
// ),
// );
//
// if (picked != null) {
// final hours = picked.hourOfPeriod;
// final minutes = picked.minute.toString().padLeft(2, '0');
// final period = picked.period == DayPeriod.am ? 'AM' : 'PM';
// controller.text = '$hours:$minutes $period';
// }
// }
import 'package:flutter/material.dart';
Future<void> setTime(TextEditingController controller, BuildContext context) async {
TimeOfDay initialTime = TimeOfDay.now();
if (controller.text.isNotEmpty) {
final timeParts = controller.text.split(' ');
final time = timeParts[0].split(':');
final period = timeParts.length > 1 ? timeParts[1] : '';
int hour = int.parse(time[0]);
if (period == 'PM' && hour != 12) hour += 12;
if (period == 'AM' && hour == 12) hour = 0;
initialTime = TimeOfDay(hour: hour, minute: int.parse(time[1]));
}
final TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: initialTime,
builder: (context, child) {
final theme = Theme.of(context);
return Theme(
data: theme.copyWith(
colorScheme: theme.colorScheme.copyWith(
primary: theme.colorScheme.primary, // active selection color
onPrimary: Colors.white, // text color on primary (e.g., white text)
surface: theme.colorScheme.surface, // dialog background
onSurface: theme.colorScheme.onSurface, // default text color
),
timePickerTheme: TimePickerThemeData(
backgroundColor: theme.dialogBackgroundColor,
hourMinuteTextColor: theme.colorScheme.onSurface,
dialBackgroundColor: theme.colorScheme.surfaceVariant,
dialHandColor: theme.colorScheme.primary,
),
),
child: MediaQuery(
data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: false),
child: child!,
),
);
},
);
if (picked != null) {
final hours = picked.hourOfPeriod == 0 ? 12 : picked.hourOfPeriod;
final minutes = picked.minute.toString().padLeft(2, '0');
final period = picked.period == DayPeriod.am ? 'AM' : 'PM';
controller.text = '$hours:$minutes $period';
}
}