Add alarms to tasks, admin role from DB
This commit is contained in:
@@ -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` - только просмотр
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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(
|
||||||
icon: const Icon(Icons.admin_panel_settings),
|
builder: (context, ref, _) {
|
||||||
onPressed: () => context.push('/home/admin'),
|
final user = ref.watch(currentUserProvider);
|
||||||
tooltip: 'Админка',
|
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(
|
IconButton(
|
||||||
icon: const Icon(Icons.auto_awesome),
|
icon: const Icon(Icons.auto_awesome),
|
||||||
|
|||||||
@@ -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: 'Будильники',
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user