From 28ef21b2f4cc531bb024df0d3598653ef16810bc Mon Sep 17 00:00:00 2001 From: AlexIves16 Date: Sat, 28 Mar 2026 19:39:25 +0500 Subject: [PATCH] Add alarms to tasks, admin role from DB --- SETUP_ADMINS.md | 65 ++++++++++++++++++++++++++++ lib/models/task_model.dart | 67 +++++++++++++++++++++++++---- lib/screens/admin/admin_screen.dart | 2 +- lib/screens/dashboard_screen.dart | 20 +++++++-- lib/screens/home_screen.dart | 6 --- lib/screens/task_form_screen.dart | 1 + 6 files changed, 141 insertions(+), 20 deletions(-) create mode 100644 SETUP_ADMINS.md diff --git a/SETUP_ADMINS.md b/SETUP_ADMINS.md new file mode 100644 index 0000000..0a0136a --- /dev/null +++ b/SETUP_ADMINS.md @@ -0,0 +1,65 @@ +# πŸ“ Настройка Π°Π΄ΠΌΠΈΠ½ΠΎΠ² Π² Firestore + +## Админы NANAXE CRM + +### Email Π°Π΄ΠΌΠΈΠ½ΠΎΠ²: +1. `almalexyz@gmail.com` +2. `ormix16@gmail.com` + +## Как Π½Π°Π·Π½Π°Ρ‡ΠΈΡ‚ΡŒ Π°Π΄ΠΌΠΈΠ½Π°: + +### Бпособ 1: Π§Π΅Ρ€Π΅Π· Firebase Console + +1. ΠžΡ‚ΠΊΡ€ΠΎΠΉ https://console.firebase.google.com/project/nanax26-1f33b/firestore +2. Найди ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΡŽ `users` +3. Найди Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ ΠΏΠΎ email +4. ИзмСни ΠΏΠΎΠ»Π΅ `role` Π½Π° `"admin"` + +### Бпособ 2: Π§Π΅Ρ€Π΅Π· Firestore запрос + +```javascript +// Π’ Firebase Console β†’ Firestore β†’ Start collection +db.collection('users').where('email', '==', 'almalexyz@gmail.com').get() + .then(snapshot => { + snapshot.docs.forEach(doc => { + doc.ref.update({ role: 'admin' }); + }); + }); + +db.collection('users').where('email', '==', 'ormix16@gmail.com').get() + .then(snapshot => { + snapshot.docs.forEach(doc => { + doc.ref.update({ role: 'admin' }); + }); + }); +``` + +### Бпособ 3: Π§Π΅Ρ€Π΅Π· Cloud Functions (автоматичСски) + +```javascript +exports.setOnAdminLogin = functions.auth.user().onCreate(async (user) => { + const adminEmails = ['almalexyz@gmail.com', 'ormix16@gmail.com']; + + if (adminEmails.includes(user.email)) { + await admin.firestore().collection('users').doc(user.uid).update({ + role: 'admin' + }); + } +}); +``` + +## ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Ρ€ΠΎΠ»ΠΈ: + +Π’ ΠΊΠΎΠ΄Π΅ прилоТСния: +```dart +final user = ref.watch(currentUserProvider); +if (user?.isAdmin ?? false) { + // ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Π°Π΄ΠΌΠΈΠ½ΠΊΡƒ +} +``` + +## Π ΠΎΠ»ΠΈ: + +- `admin` - ΠΏΠΎΠ»Π½Ρ‹ΠΉ доступ ΠΊΠΎ всСм функциям +- `member` - созданиС Π·Π°Π΄Π°Ρ‡, Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ своих +- `viewer` - Ρ‚ΠΎΠ»ΡŒΠΊΠΎ просмотр diff --git a/lib/models/task_model.dart b/lib/models/task_model.dart index c762933..0934b29 100644 --- a/lib/models/task_model.dart +++ b/lib/models/task_model.dart @@ -1,5 +1,46 @@ import 'package:cloud_firestore/cloud_firestore.dart'; +// Π”Π°Π½Π½Ρ‹Π΅ Π±ΡƒΠ΄ΠΈΠ»ΡŒΠ½ΠΈΠΊΠ° +class AlarmData { + final String id; + final DateTime dateTime; + final bool isRepeating; + final String repeatPattern; + final List repeatDays; + final bool isEnabled; + + AlarmData({ + required this.id, + required this.dateTime, + this.isRepeating = false, + this.repeatPattern = 'daily', + this.repeatDays = const [], + this.isEnabled = true, + }); + + factory AlarmData.fromMap(Map map) { + return AlarmData( + id: map['id'] ?? '', + dateTime: (map['dateTime'] as Timestamp).toDate(), + isRepeating: map['isRepeating'] ?? false, + repeatPattern: map['repeatPattern'] ?? 'daily', + repeatDays: List.from(map['repeatDays'] ?? []), + isEnabled: map['isEnabled'] ?? true, + ); + } + + Map toMap() { + return { + 'id': id, + 'dateTime': Timestamp.fromDate(dateTime), + 'isRepeating': isRepeating, + 'repeatPattern': repeatPattern, + 'repeatDays': repeatDays, + 'isEnabled': isEnabled, + }; + } +} + enum TaskStatus { todo, inProgress, @@ -29,6 +70,7 @@ class TaskModel { final DateTime? dueDate; final DateTime? startDate; final DateTime? completedDate; + final List alarms; // Π‘ΡƒΠ΄ΠΈΠ»ΡŒΠ½ΠΈΠΊΠΈ для Π·Π°Π΄Π°Ρ‡ΠΈ final int estimatedHours; final int spentHours; final Map aiMetadata; // AI-generated suggestions @@ -49,6 +91,7 @@ class TaskModel { this.dueDate, this.startDate, this.completedDate, + this.alarms = const [], this.estimatedHours = 0, this.spentHours = 0, this.aiMetadata = const {}, @@ -75,15 +118,19 @@ class TaskModel { assigneeId: data['assigneeId'], creatorId: data['creatorId'], tags: List.from(data['tags'] ?? []), - dueDate: data['dueDate'] != null - ? (data['dueDate'] as Timestamp).toDate() + dueDate: data['dueDate'] != null + ? (data['dueDate'] as Timestamp).toDate() : null, - startDate: data['startDate'] != null - ? (data['startDate'] as Timestamp).toDate() + startDate: data['startDate'] != null + ? (data['startDate'] as Timestamp).toDate() : null, - completedDate: data['completedDate'] != null - ? (data['completedDate'] as Timestamp).toDate() + completedDate: data['completedDate'] != null + ? (data['completedDate'] as Timestamp).toDate() : null, + alarms: (data['alarms'] as List?) + ?.map((a) => AlarmData.fromMap(a as Map)) + .toList() ?? + [], estimatedHours: data['estimatedHours'] ?? 0, spentHours: data['spentHours'] ?? 0, aiMetadata: data['aiMetadata'] ?? {}, @@ -105,9 +152,9 @@ class TaskModel { 'tags': tags, 'dueDate': dueDate != null ? Timestamp.fromDate(dueDate!) : null, 'startDate': startDate != null ? Timestamp.fromDate(startDate!) : null, - 'completedDate': completedDate != null - ? Timestamp.fromDate(completedDate!) - : null, + 'completedDate': + completedDate != null ? Timestamp.fromDate(completedDate!) : null, + 'alarms': alarms.map((a) => a.toMap()).toList(), 'estimatedHours': estimatedHours, 'spentHours': spentHours, 'aiMetadata': aiMetadata, @@ -130,6 +177,7 @@ class TaskModel { DateTime? dueDate, DateTime? startDate, DateTime? completedDate, + List? alarms, int? estimatedHours, int? spentHours, Map? aiMetadata, @@ -150,6 +198,7 @@ class TaskModel { dueDate: dueDate ?? this.dueDate, startDate: startDate ?? this.startDate, completedDate: completedDate ?? this.completedDate, + alarms: alarms ?? this.alarms, estimatedHours: estimatedHours ?? this.estimatedHours, spentHours: spentHours ?? this.spentHours, aiMetadata: aiMetadata ?? this.aiMetadata, diff --git a/lib/screens/admin/admin_screen.dart b/lib/screens/admin/admin_screen.dart index aaf0d35..1d5955e 100644 --- a/lib/screens/admin/admin_screen.dart +++ b/lib/screens/admin/admin_screen.dart @@ -10,7 +10,7 @@ class AdminScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final user = ref.watch(currentUserProvider); - final isAdmin = user?.isAdmin ?? false; + final isAdmin = user?.role == UserRole.admin; return Scaffold( appBar: AppBar( diff --git a/lib/screens/dashboard_screen.dart b/lib/screens/dashboard_screen.dart index 52826c8..231216f 100644 --- a/lib/screens/dashboard_screen.dart +++ b/lib/screens/dashboard_screen.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import '../../providers/auth_provider.dart'; class DashboardScreen extends StatelessWidget { const DashboardScreen({super.key}); @@ -10,10 +12,20 @@ class DashboardScreen extends StatelessWidget { appBar: AppBar( title: const Text('NANAXE CRM'), actions: [ - IconButton( - icon: const Icon(Icons.admin_panel_settings), - onPressed: () => context.push('/home/admin'), - tooltip: 'Админка', + Consumer( + builder: (context, ref, _) { + final user = ref.watch(currentUserProvider); + final isAdmin = user?.role == UserRole.admin; + + if (isAdmin ?? false) { + return IconButton( + icon: const Icon(Icons.admin_panel_settings), + onPressed: () => context.push('/home/admin'), + tooltip: 'Админка', + ); + } + return const SizedBox.shrink(); + }, ), IconButton( icon: const Icon(Icons.auto_awesome), diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 4692ee7..7b8970b 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -20,7 +20,6 @@ class _HomeScreenState extends State { const TasksScreen(), const MindMapScreen(), const CalendarScreen(), - const AlarmsScreen(), ]; @override @@ -55,11 +54,6 @@ class _HomeScreenState extends State { selectedIcon: Icon(Icons.calendar_today), label: 'ΠšΠ°Π»Π΅Π½Π΄Π°Ρ€ΡŒ', ), - NavigationDestination( - icon: Icon(Icons.alarm_outlined), - selectedIcon: Icon(Icons.alarm), - label: 'Π‘ΡƒΠ΄ΠΈΠ»ΡŒΠ½ΠΈΠΊΠΈ', - ), ], ), ); diff --git a/lib/screens/task_form_screen.dart b/lib/screens/task_form_screen.dart index 8527205..a9347cb 100644 --- a/lib/screens/task_form_screen.dart +++ b/lib/screens/task_form_screen.dart @@ -26,6 +26,7 @@ class _TaskFormScreenState extends ConsumerState { TaskPriority _priority = TaskPriority.medium; DateTime? _dueDate; int _estimatedHours = 0; + List _alarms = []; bool _isGenerating = false; @override