chore: استيراد أولي من سيرو (ecfe7568) — بلا أي تعديل
نسخة كاملة من مستودع سيرو عند ecfe7568 لتكون أساس تطبيق «انطلق». نُسخ المتعقَّب في git فقط (12,509 ملفاً / 302 م.ب) بـ git archive، لا `cp -r` — فاستُثنيت تلقائياً مخلفات البناء (build · node_modules · .dart_tool · .gradle · Pods ≈ 10.7 غ.ب) وكل ما يستثنيه .gitignore. هذا الكوميت **بلا أي تعديل عمداً** حتى يكون كل ما يليه فرقاً مقروءاً مقابل سيرو الأصلي. سيرو نفسه لم يُمسّ. ⚠️ لا يبني بعد: `.env` و`lib/env/env.g.dart` غير متعقَّبين في سيرو (وهذا صحيح — أسرار لكل مستأجر). كل تطبيق فلاتر هنا يحتاج .env خاصاً بانطلق ثم توليد env.g.dart عبر build_runner. لا تُنسخ أسرار سيرو. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
// org_admins_page.dart — متابعة إدارة المؤسسة وإضافة المشرفين (لوحة إدارة سيرو)
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../constant/colors.dart';
|
||||
import '../../controller/transit/transit_admin_controller.dart';
|
||||
|
||||
class TransitOrgAdminsPage extends StatefulWidget {
|
||||
final int orgId;
|
||||
final String orgName;
|
||||
const TransitOrgAdminsPage({super.key, required this.orgId, required this.orgName});
|
||||
|
||||
@override
|
||||
State<TransitOrgAdminsPage> createState() => _TransitOrgAdminsPageState();
|
||||
}
|
||||
|
||||
class _TransitOrgAdminsPageState extends State<TransitOrgAdminsPage> {
|
||||
final _name = TextEditingController();
|
||||
final _phone = TextEditingController();
|
||||
String _role = 'transport_manager';
|
||||
|
||||
final _roles = const {
|
||||
'owner': 'مالك',
|
||||
'transport_manager': 'مدير نقل',
|
||||
'dispatcher': 'منسّق',
|
||||
};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
Get.find<TransitAdminController>().fetchOrgAdmins(widget.orgId);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<TransitAdminController>(
|
||||
builder: (c) => Scaffold(
|
||||
backgroundColor: AppColor.bg,
|
||||
appBar: AppBar(
|
||||
backgroundColor: AppColor.bg,
|
||||
elevation: 0,
|
||||
title: Text('مشرفو ${widget.orgName}',
|
||||
style: const TextStyle(color: AppColor.textPrimary)),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
backgroundColor: AppColor.accent,
|
||||
onPressed: () => _showAddAdminSheet(context, c),
|
||||
child: const Icon(Icons.person_add_alt_1, color: Colors.white),
|
||||
),
|
||||
body: c.isLoadingAdmins
|
||||
? const Center(child: CircularProgressIndicator(color: AppColor.accent))
|
||||
: c.orgAdmins.isEmpty
|
||||
? const Center(
|
||||
child: Text('لا يوجد مشرفون بعد', style: TextStyle(color: AppColor.textSecondary)))
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: c.orgAdmins.length,
|
||||
itemBuilder: (_, i) => _adminCard(c, c.orgAdmins[i]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _adminCard(TransitAdminController c, Map<String, dynamic> admin) {
|
||||
final isActive = admin['is_active'].toString() == '1';
|
||||
return Card(
|
||||
color: AppColor.surface,
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: AppColor.accentSoft,
|
||||
child: Icon(Icons.person, color: isActive ? AppColor.accent : AppColor.textMuted),
|
||||
),
|
||||
title: Text(admin['name']?.toString() ?? '',
|
||||
style: const TextStyle(color: AppColor.textPrimary, fontWeight: FontWeight.bold)),
|
||||
subtitle: Text(
|
||||
'${admin['phone'] ?? ''} · ${_roles[admin['role']] ?? admin['role']}',
|
||||
style: const TextStyle(color: AppColor.textSecondary, fontSize: 12),
|
||||
),
|
||||
trailing: Switch(
|
||||
value: isActive,
|
||||
activeThumbColor: AppColor.success,
|
||||
onChanged: (v) => c.toggleOrgAdmin(widget.orgId, int.parse(admin['id'].toString()), v),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showAddAdminSheet(BuildContext context, TransitAdminController c) {
|
||||
_name.clear();
|
||||
_phone.clear();
|
||||
_role = 'transport_manager';
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: AppColor.surface,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
builder: (ctx) => StatefulBuilder(
|
||||
builder: (ctx, setSheetState) => GetBuilder<TransitAdminController>(
|
||||
builder: (c) => Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 20,
|
||||
right: 20,
|
||||
top: 20,
|
||||
bottom: MediaQuery.of(ctx).viewInsets.bottom + 20,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('إضافة مشرف جديد',
|
||||
style: TextStyle(
|
||||
color: AppColor.textPrimary, fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _name,
|
||||
style: const TextStyle(color: AppColor.textPrimary),
|
||||
decoration: _inputDecoration('اسم المشرف'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: _phone,
|
||||
keyboardType: TextInputType.phone,
|
||||
style: const TextStyle(color: AppColor.textPrimary),
|
||||
decoration: _inputDecoration('رقم الهاتف'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
DropdownButtonFormField<String>(
|
||||
initialValue: _role,
|
||||
dropdownColor: AppColor.surface,
|
||||
style: const TextStyle(color: AppColor.textPrimary),
|
||||
decoration: _inputDecoration('الصلاحية'),
|
||||
items: _roles.entries
|
||||
.map((e) => DropdownMenuItem(value: e.key, child: Text(e.value)))
|
||||
.toList(),
|
||||
onChanged: (v) => setSheetState(() => _role = v!),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColor.accent,
|
||||
minimumSize: const Size.fromHeight(48),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
onPressed: c.isSavingAdmin
|
||||
? null
|
||||
: () async {
|
||||
if (_name.text.trim().isEmpty || _phone.text.trim().isEmpty) {
|
||||
Get.snackbar('مواصلاتي', 'الرجاء تعبئة كل الحقول');
|
||||
return;
|
||||
}
|
||||
final ok = await c.addOrgAdmin(
|
||||
orgId: widget.orgId,
|
||||
name: _name.text.trim(),
|
||||
phone: _phone.text.trim(),
|
||||
role: _role,
|
||||
);
|
||||
if (ok && ctx.mounted) Navigator.pop(ctx);
|
||||
},
|
||||
child: c.isSavingAdmin
|
||||
? const SizedBox(
|
||||
height: 20, width: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
|
||||
: const Text('إضافة', style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
InputDecoration _inputDecoration(String label) => InputDecoration(
|
||||
labelText: label,
|
||||
labelStyle: const TextStyle(color: AppColor.textSecondary),
|
||||
filled: true,
|
||||
fillColor: AppColor.surfaceElevated,
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user