Add alarms to tasks, admin role from DB

This commit is contained in:
2026-03-28 19:39:25 +05:00
parent a2f09de04d
commit 28ef21b2f4
6 changed files with 141 additions and 20 deletions
+65
View File
@@ -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` - только просмотр
+52 -3
View File
@@ -1,5 +1,46 @@
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
// Данные будильника
class AlarmData {
final String id;
final DateTime dateTime;
final bool isRepeating;
final String repeatPattern;
final List<int> 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<String, dynamic> map) {
return AlarmData(
id: map['id'] ?? '',
dateTime: (map['dateTime'] as Timestamp).toDate(),
isRepeating: map['isRepeating'] ?? false,
repeatPattern: map['repeatPattern'] ?? 'daily',
repeatDays: List<int>.from(map['repeatDays'] ?? []),
isEnabled: map['isEnabled'] ?? true,
);
}
Map<String, dynamic> toMap() {
return {
'id': id,
'dateTime': Timestamp.fromDate(dateTime),
'isRepeating': isRepeating,
'repeatPattern': repeatPattern,
'repeatDays': repeatDays,
'isEnabled': isEnabled,
};
}
}
enum TaskStatus { enum TaskStatus {
todo, todo,
inProgress, inProgress,
@@ -29,6 +70,7 @@ class TaskModel {
final DateTime? dueDate; final DateTime? dueDate;
final DateTime? startDate; final DateTime? startDate;
final DateTime? completedDate; final DateTime? completedDate;
final List<AlarmData> alarms; // Будильники для задачи
final int estimatedHours; final int estimatedHours;
final int spentHours; final int spentHours;
final Map<String, dynamic> aiMetadata; // AI-generated suggestions final Map<String, dynamic> aiMetadata; // AI-generated suggestions
@@ -49,6 +91,7 @@ class TaskModel {
this.dueDate, this.dueDate,
this.startDate, this.startDate,
this.completedDate, this.completedDate,
this.alarms = const [],
this.estimatedHours = 0, this.estimatedHours = 0,
this.spentHours = 0, this.spentHours = 0,
this.aiMetadata = const {}, this.aiMetadata = const {},
@@ -84,6 +127,10 @@ class TaskModel {
completedDate: data['completedDate'] != null completedDate: data['completedDate'] != null
? (data['completedDate'] as Timestamp).toDate() ? (data['completedDate'] as Timestamp).toDate()
: null, : null,
alarms: (data['alarms'] as List<dynamic>?)
?.map((a) => AlarmData.fromMap(a as Map<String, dynamic>))
.toList() ??
[],
estimatedHours: data['estimatedHours'] ?? 0, estimatedHours: data['estimatedHours'] ?? 0,
spentHours: data['spentHours'] ?? 0, spentHours: data['spentHours'] ?? 0,
aiMetadata: data['aiMetadata'] ?? {}, aiMetadata: data['aiMetadata'] ?? {},
@@ -105,9 +152,9 @@ class TaskModel {
'tags': tags, 'tags': tags,
'dueDate': dueDate != null ? Timestamp.fromDate(dueDate!) : null, 'dueDate': dueDate != null ? Timestamp.fromDate(dueDate!) : null,
'startDate': startDate != null ? Timestamp.fromDate(startDate!) : null, 'startDate': startDate != null ? Timestamp.fromDate(startDate!) : null,
'completedDate': completedDate != null 'completedDate':
? Timestamp.fromDate(completedDate!) completedDate != null ? Timestamp.fromDate(completedDate!) : null,
: null, 'alarms': alarms.map((a) => a.toMap()).toList(),
'estimatedHours': estimatedHours, 'estimatedHours': estimatedHours,
'spentHours': spentHours, 'spentHours': spentHours,
'aiMetadata': aiMetadata, 'aiMetadata': aiMetadata,
@@ -130,6 +177,7 @@ class TaskModel {
DateTime? dueDate, DateTime? dueDate,
DateTime? startDate, DateTime? startDate,
DateTime? completedDate, DateTime? completedDate,
List<AlarmData>? alarms,
int? estimatedHours, int? estimatedHours,
int? spentHours, int? spentHours,
Map<String, dynamic>? aiMetadata, Map<String, dynamic>? aiMetadata,
@@ -150,6 +198,7 @@ class TaskModel {
dueDate: dueDate ?? this.dueDate, dueDate: dueDate ?? this.dueDate,
startDate: startDate ?? this.startDate, startDate: startDate ?? this.startDate,
completedDate: completedDate ?? this.completedDate, completedDate: completedDate ?? this.completedDate,
alarms: alarms ?? this.alarms,
estimatedHours: estimatedHours ?? this.estimatedHours, estimatedHours: estimatedHours ?? this.estimatedHours,
spentHours: spentHours ?? this.spentHours, spentHours: spentHours ?? this.spentHours,
aiMetadata: aiMetadata ?? this.aiMetadata, aiMetadata: aiMetadata ?? this.aiMetadata,
+1 -1
View File
@@ -10,7 +10,7 @@ class AdminScreen extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(currentUserProvider); final user = ref.watch(currentUserProvider);
final isAdmin = user?.isAdmin ?? false; final isAdmin = user?.role == UserRole.admin;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
+13 -1
View File
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../../providers/auth_provider.dart';
class DashboardScreen extends StatelessWidget { class DashboardScreen extends StatelessWidget {
const DashboardScreen({super.key}); const DashboardScreen({super.key});
@@ -10,10 +12,20 @@ class DashboardScreen extends StatelessWidget {
appBar: AppBar( appBar: AppBar(
title: const Text('NANAXE CRM'), title: const Text('NANAXE CRM'),
actions: [ actions: [
IconButton( 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), icon: const Icon(Icons.admin_panel_settings),
onPressed: () => context.push('/home/admin'), onPressed: () => context.push('/home/admin'),
tooltip: 'Админка', tooltip: 'Админка',
);
}
return const SizedBox.shrink();
},
), ),
IconButton( IconButton(
icon: const Icon(Icons.auto_awesome), icon: const Icon(Icons.auto_awesome),
-6
View File
@@ -20,7 +20,6 @@ class _HomeScreenState extends State<HomeScreen> {
const TasksScreen(), const TasksScreen(),
const MindMapScreen(), const MindMapScreen(),
const CalendarScreen(), const CalendarScreen(),
const AlarmsScreen(),
]; ];
@override @override
@@ -55,11 +54,6 @@ class _HomeScreenState extends State<HomeScreen> {
selectedIcon: Icon(Icons.calendar_today), selectedIcon: Icon(Icons.calendar_today),
label: 'Календарь', label: 'Календарь',
), ),
NavigationDestination(
icon: Icon(Icons.alarm_outlined),
selectedIcon: Icon(Icons.alarm),
label: 'Будильники',
),
], ],
), ),
); );
+1
View File
@@ -26,6 +26,7 @@ class _TaskFormScreenState extends ConsumerState<TaskFormScreen> {
TaskPriority _priority = TaskPriority.medium; TaskPriority _priority = TaskPriority.medium;
DateTime? _dueDate; DateTime? _dueDate;
int _estimatedHours = 0; int _estimatedHours = 0;
List<AlarmData> _alarms = [];
bool _isGenerating = false; bool _isGenerating = false;
@override @override