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,178 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mobile_pos/Screens/product_model/provider/models_provider.dart';
import 'package:mobile_pos/Screens/product_model/repo/product_models_repo.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import '../../constant.dart';
import '../../http_client/custome_http_client.dart';
import '../../service/check_user_role_permission_provider.dart';
import 'model/product_models_model.dart';
class AddProductModel extends ConsumerStatefulWidget {
const AddProductModel({super.key, this.editData});
final Data? editData;
bool get isEditMode => editData != null;
@override
ConsumerState<AddProductModel> createState() => _AddProductModelState();
}
class _AddProductModelState extends ConsumerState<AddProductModel> {
late final TextEditingController nameController;
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
bool isActive = true;
@override
void initState() {
super.initState();
nameController = TextEditingController();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (widget.isEditMode) {
setState(() {
nameController.text = widget.editData?.name ?? '';
isActive = widget.editData?.status == 1;
});
}
});
}
@override
void dispose() {
nameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final permissionService = PermissionService(ref);
final _lang = lang.S.of(context);
return Scaffold(
backgroundColor: kWhite,
appBar: AppBar(
centerTitle: true,
title: Text(widget.isEditMode ? _lang.editModel : _lang.addNewModel),
),
body: Form(
key: formKey,
child: SingleChildScrollView(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
// Model Name Input
TextFormField(
controller: nameController,
validator: (value) {
if (value == null || value.isEmpty) {
return _lang.pleaseEnterValidName;
}
return null;
},
decoration: InputDecoration(
border: OutlineInputBorder(),
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: _lang.modelName,
hintText: _lang.enterModelName,
),
),
const SizedBox(height: 8),
// Status Toggle
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(_lang.status),
SizedBox(
height: 32,
width: 44,
child: FittedBox(
child: Switch.adaptive(
value: isActive,
onChanged: (value) => setState(() => isActive = value),
),
),
),
],
),
const SizedBox(height: 16),
// Submit Button
ElevatedButton(
style: OutlinedButton.styleFrom(
disabledBackgroundColor: theme.colorScheme.primary.withAlpha(40),
),
onPressed: () async {
if (widget.editData == null) {
if (!permissionService.hasPermission(Permit.productModelsCreate.value)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.red,
content: Text(_lang.youDoNotHavePermissionToCreateModel),
),
);
return;
}
} else {
if (!permissionService.hasPermission(Permit.productModelsUpdate.value)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.red,
content: Text(_lang.youDoNotHavePermissionToUpdateModel),
),
);
return;
}
}
if (formKey.currentState?.validate() ?? false) {
final repo = ProductModelsRepo();
final data = CreateModelsModel(
name: nameController.text,
status: isActive ? '1' : '0',
modelId: widget.editData?.id.toString(),
);
bool success =
widget.isEditMode ? await repo.updateModels(data: data) : await repo.createModels(data: data);
if (success) {
EasyLoading.showSuccess(
widget.isEditMode ? _lang.modelUpdateSuccessfully : _lang.modelCreatedSuccessfully,
);
ref.refresh(fetchModelListProvider);
Navigator.pop(context);
}
}
},
child: Text(
_lang.save,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.primaryContainer,
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
),
],
),
),
),
);
}
}
class CreateModelsModel {
CreateModelsModel({
this.modelId,
this.name,
this.status,
});
String? modelId;
String? name;
String? status;
}

View File

@@ -0,0 +1,64 @@
class ProductModelsModel {
ProductModelsModel({
this.message,
this.data,
});
ProductModelsModel.fromJson(dynamic json) {
message = json['message'];
if (json['data'] != null) {
data = [];
json['data'].forEach((v) {
data?.add(Data.fromJson(v));
});
}
}
String? message;
List<Data>? data;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['message'] = message;
if (data != null) {
map['data'] = data?.map((v) => v.toJson()).toList();
}
return map;
}
}
class Data {
Data({
this.id,
this.name,
this.businessId,
this.status,
this.createdAt,
this.updatedAt,
});
Data.fromJson(dynamic json) {
id = json['id'];
name = json['name'];
businessId = json['business_id'];
status = json['status'];
createdAt = json['created_at'];
updatedAt = json['updated_at'];
}
num? id;
String? name;
num? businessId;
num? status;
String? createdAt;
String? updatedAt;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
map['name'] = name;
map['business_id'] = businessId;
map['status'] = status;
map['created_at'] = createdAt;
map['updated_at'] = updatedAt;
return map;
}
}

View File

@@ -0,0 +1,243 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:iconly/iconly.dart';
import 'package:mobile_pos/Screens/product_model/provider/models_provider.dart';
import 'package:mobile_pos/Screens/product_model/repo/product_models_repo.dart';
import 'package:mobile_pos/constant.dart';
import 'package:mobile_pos/core/theme/_app_colors.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import 'package:mobile_pos/widgets/empty_widget/_empty_widget.dart';
import '../../GlobalComponents/glonal_popup.dart';
import '../../http_client/custome_http_client.dart';
import '../../service/check_user_role_permission_provider.dart';
import 'add_products_models.dart';
class ProductModelList extends StatefulWidget {
const ProductModelList({super.key, required this.fromProductList});
final bool fromProductList;
@override
ProductModelListState createState() => ProductModelListState();
}
class ProductModelListState extends State<ProductModelList> {
String search = '';
@override
Widget build(BuildContext context) {
final _lang = lang.S.of(context);
return GlobalPopup(
child: Scaffold(
backgroundColor: kWhite,
appBar: AppBar(
title: Text(_lang.models),
iconTheme: const IconThemeData(color: Colors.black),
centerTitle: true,
backgroundColor: Colors.white,
elevation: 0.0,
),
body: Consumer(builder: (context, ref, __) {
final modelData = ref.watch(fetchModelListProvider);
final permissionService = PermissionService(ref);
if (!permissionService.hasPermission(Permit.productModelsRead.value)) {
return Center(child: PermitDenyWidget());
}
return SingleChildScrollView(
physics: NeverScrollableScrollPhysics(),
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Row(
children: [
Expanded(
child: TextFormField(
decoration: InputDecoration(
border: const OutlineInputBorder(),
hintText: lang.S.of(context).search,
prefixIcon: Icon(
Icons.search,
color: kGreyTextColor.withOpacity(0.5),
),
),
onChanged: (value) {
setState(() {
search = value;
});
},
),
),
const SizedBox(width: 10.0),
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => AddProductModel()),
);
},
child: Container(
height: 48.0,
width: 48,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
border: Border.all(color: kMainColor),
),
child: const Icon(Icons.add, color: kMainColor),
),
),
],
),
),
modelData.when(
data: (snapshot) {
final allModels = snapshot.data ?? [];
final filteredModels = allModels.where((model) {
final name = (model.name ?? '').toLowerCase();
return name.contains(search.toLowerCase());
}).toList();
if (filteredModels.isEmpty) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 50),
child: EmptyWidgetUpdated(
message: TextSpan(text: lang.S.of(context).noDataFound),
),
);
}
return ListView.builder(
itemCount: filteredModels.length,
physics: AlwaysScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, i) {
final model = filteredModels[i];
return ListCardWidget(
onSelect: widget.fromProductList
? () {}
: () {
Navigator.pop(context, model);
},
title: model.name?.toString() ?? 'n/a',
onDelete: () async {
if (!permissionService.hasPermission(Permit.productModelsDelete.value)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.red,
content: Text(_lang.youDoNotHavePermissionDeleteModel),
),
);
return;
}
ProductModelsRepo repo = ProductModelsRepo();
bool success = await repo.deleteModel(id: model.id?.toString() ?? '');
if (success) {
ref.refresh(fetchModelListProvider);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(_lang.deletedSuccessFully)),
);
}
},
onEdit: () async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddProductModel(editData: model),
),
);
},
);
},
);
},
error: (_, __) => const SizedBox.shrink(),
loading: () => const Center(
child: SizedBox(height: 40, width: 40, child: CircularProgressIndicator()),
),
),
],
),
);
}),
),
);
}
}
class ListCardWidget extends StatelessWidget {
const ListCardWidget({
super.key,
this.onEdit,
this.onDelete,
required this.title,
this.onSelect,
});
final void Function()? onEdit;
final void Function()? onDelete;
final void Function()? onSelect;
final String title;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return InkWell(
onTap: onSelect,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8),
margin: const EdgeInsets.symmetric(horizontal: 16),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(color: Color(0xffD8D8D8)),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodyLarge,
),
),
Row(
children: [
IconButton.filledTonal(
onPressed: onEdit,
style: IconButton.styleFrom(
padding: EdgeInsets.zero,
backgroundColor: Colors.white.withValues(alpha: 0.25),
),
visualDensity: const VisualDensity(horizontal: -2, vertical: -2),
iconSize: 20,
icon: const Icon(
IconlyLight.edit,
color: DAppColors.kSecondary,
),
),
IconButton.filledTonal(
onPressed: onDelete,
style: IconButton.styleFrom(
padding: EdgeInsets.zero,
backgroundColor: Colors.white.withValues(alpha: 0.25),
),
visualDensity: const VisualDensity(horizontal: -2, vertical: -2),
iconSize: 20,
icon: const Icon(
IconlyLight.delete,
color: Colors.redAccent,
),
),
],
),
// const Spacer(),
],
),
),
);
}
}

View File

@@ -0,0 +1,11 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../model/product_models_model.dart';
import '../repo/product_models_repo.dart';
ProductModelsRepo repo = ProductModelsRepo();
// fetch models list
final fetchModelListProvider = FutureProvider<ProductModelsModel>((ref) {
return repo.fetchModelsList();
});

View File

@@ -0,0 +1,144 @@
import 'dart:convert';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:http/http.dart' as http;
import 'package:nb_utils/nb_utils.dart';
import '../../../Const/api_config.dart';
import '../../../Repository/constant_functions.dart';
import '../../../http_client/customer_http_client_get.dart';
import '../model/product_models_model.dart';
import '../add_products_models.dart';
class ProductModelsRepo {
// Create Model
Future<bool> createModels({required CreateModelsModel data}) async {
EasyLoading.show(status: 'Creating Models...');
final url = Uri.parse('${APIConfig.url}/product-models');
// Create a multipart request
var request = http.MultipartRequest('POST', url);
request.headers.addAll({
'Accept': 'application/json',
'Authorization': await getAuthToken(),
});
request.fields['name'] = data.name.toString();
request.fields['status'] = data.status.toString();
try {
var response = await request.send();
var responseData = await http.Response.fromStream(response);
EasyLoading.dismiss();
print('Model create ${response.statusCode}');
print('Model create ${data.status}');
if (response.statusCode == 200) {
return true;
} else {
var data = jsonDecode(responseData.body);
EasyLoading.showError(data['message'] ?? 'Failed to create Model');
print('Error: ${data['message']}');
return false;
}
} catch (e) {
EasyLoading.dismiss();
EasyLoading.showError('Error: ${e.toString()}');
print('Error: ${e.toString()}');
return false;
}
}
// models List
Future<ProductModelsModel> fetchModelsList() async {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final prefs = await SharedPreferences.getInstance();
String token = prefs.getString('token') ?? '';
final url = Uri.parse('${APIConfig.url}/product-models');
try {
var response = await clientGet.get(url: url);
EasyLoading.dismiss();
if (response.statusCode == 200) {
var jsonData = jsonDecode(response.body);
return ProductModelsModel.fromJson(jsonData);
} else {
var data = jsonDecode(response.body);
EasyLoading.showError(data['message'] ?? 'Failed to fetch models');
throw Exception(data['message'] ?? 'Failed to fetch models');
}
} catch (e) {
// Hide loading indicator and show error
EasyLoading.dismiss();
EasyLoading.showError('Error: ${e.toString()}');
throw Exception('Error: ${e.toString()}');
}
}
// Update Model
Future<bool> updateModels({required CreateModelsModel data}) async {
EasyLoading.show(status: 'Updating Model...');
final url = Uri.parse('${APIConfig.url}/product-models/${data.modelId}');
// Create a multipart request
var request = http.MultipartRequest('POST', url);
request.headers.addAll({
'Accept': 'application/json',
'Authorization': await getAuthToken(),
});
request.fields['name'] = data.name.toString();
request.fields['status'] = data.status.toString();
request.fields['_method'] = 'put';
try {
var response = await request.send();
var responseData = await http.Response.fromStream(response);
EasyLoading.dismiss();
print(response.statusCode);
if (response.statusCode == 200) {
return true;
} else {
var data = jsonDecode(responseData.body);
EasyLoading.showError(data['message'] ?? 'Failed to update');
return false;
}
} catch (e) {
EasyLoading.dismiss();
EasyLoading.showError('Error: ${e.toString()}');
return false;
}
}
// delete warehouse
Future<bool> deleteModel({required String id}) async {
EasyLoading.show(status: 'Processing');
final prefs = await SharedPreferences.getInstance();
String token = prefs.getString('token') ?? '';
final url = Uri.parse('${APIConfig.url}/product-models/$id');
final headers = {
'Accept': 'application/json',
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
};
try {
var response = await http.delete(
url,
headers: headers,
);
EasyLoading.dismiss();
print(response.statusCode);
if (response.statusCode == 200) {
return true;
} else {
var data = jsonDecode(response.body);
EasyLoading.showError(data['message'] ?? 'Failed to delete');
print(data['message']);
return false;
}
} catch (e) {
EasyLoading.dismiss();
EasyLoading.showError('Error: ${e.toString()}');
print(e.toString());
return false;
}
}
}