Initial commit: NANAHUI CRM - Flutter Firebase CRM assistant
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
|
||||
class AlarmModel {
|
||||
final String id;
|
||||
final String title;
|
||||
final String? taskId;
|
||||
final String? eventId;
|
||||
final DateTime dateTime;
|
||||
final bool isRepeating;
|
||||
final String repeatPattern; // daily, weekly, monthly, custom
|
||||
final List<int> repeatDays; // For custom (e.g., [1, 3, 5] for Mon, Wed, Fri)
|
||||
final bool isEnabled;
|
||||
final String sound;
|
||||
final int snoozeMinutes;
|
||||
final Map<String, dynamic> metadata;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
|
||||
AlarmModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.taskId,
|
||||
this.eventId,
|
||||
required this.dateTime,
|
||||
this.isRepeating = false,
|
||||
this.repeatPattern = 'daily',
|
||||
this.repeatDays = const [],
|
||||
this.isEnabled = true,
|
||||
this.sound = 'default',
|
||||
this.snoozeMinutes = 5,
|
||||
this.metadata = const {},
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
factory AlarmModel.fromFirestore(DocumentSnapshot doc) {
|
||||
final data = doc.data() as Map<String, dynamic>;
|
||||
return AlarmModel(
|
||||
id: doc.id,
|
||||
title: data['title'] ?? '',
|
||||
taskId: data['taskId'],
|
||||
eventId: data['eventId'],
|
||||
dateTime: (data['dateTime'] as Timestamp).toDate(),
|
||||
isRepeating: data['isRepeating'] ?? false,
|
||||
repeatPattern: data['repeatPattern'] ?? 'daily',
|
||||
repeatDays: List<int>.from(data['repeatDays'] ?? []),
|
||||
isEnabled: data['isEnabled'] ?? true,
|
||||
sound: data['sound'] ?? 'default',
|
||||
snoozeMinutes: data['snoozeMinutes'] ?? 5,
|
||||
metadata: data['metadata'] ?? {},
|
||||
createdAt: (data['createdAt'] as Timestamp).toDate(),
|
||||
updatedAt: (data['updatedAt'] as Timestamp).toDate(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toFirestore() {
|
||||
return {
|
||||
'title': title,
|
||||
'taskId': taskId,
|
||||
'eventId': eventId,
|
||||
'dateTime': Timestamp.fromDate(dateTime),
|
||||
'isRepeating': isRepeating,
|
||||
'repeatPattern': repeatPattern,
|
||||
'repeatDays': repeatDays,
|
||||
'isEnabled': isEnabled,
|
||||
'sound': sound,
|
||||
'snoozeMinutes': snoozeMinutes,
|
||||
'metadata': metadata,
|
||||
'createdAt': Timestamp.fromDate(createdAt),
|
||||
'updatedAt': Timestamp.fromDate(updatedAt),
|
||||
};
|
||||
}
|
||||
|
||||
AlarmModel copyWith({
|
||||
String? id,
|
||||
String? title,
|
||||
String? taskId,
|
||||
String? eventId,
|
||||
DateTime? dateTime,
|
||||
bool? isRepeating,
|
||||
String? repeatPattern,
|
||||
List<int>? repeatDays,
|
||||
bool? isEnabled,
|
||||
String? sound,
|
||||
int? snoozeMinutes,
|
||||
Map<String, dynamic>? metadata,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
}) {
|
||||
return AlarmModel(
|
||||
id: id ?? this.id,
|
||||
title: title ?? this.title,
|
||||
taskId: taskId ?? this.taskId,
|
||||
eventId: eventId ?? this.eventId,
|
||||
dateTime: dateTime ?? this.dateTime,
|
||||
isRepeating: isRepeating ?? this.isRepeating,
|
||||
repeatPattern: repeatPattern ?? this.repeatPattern,
|
||||
repeatDays: repeatDays ?? this.repeatDays,
|
||||
isEnabled: isEnabled ?? this.isEnabled,
|
||||
sound: sound ?? this.sound,
|
||||
snoozeMinutes: snoozeMinutes ?? this.snoozeMinutes,
|
||||
metadata: metadata ?? this.metadata,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
);
|
||||
}
|
||||
|
||||
DateTime get nextOccurrence {
|
||||
if (!isRepeating) return dateTime;
|
||||
|
||||
final now = DateTime.now();
|
||||
if (dateTime.isAfter(now)) return dateTime;
|
||||
|
||||
switch (repeatPattern) {
|
||||
case 'daily':
|
||||
return dateTime.add(const Duration(days: 1));
|
||||
case 'weekly':
|
||||
return dateTime.add(const Duration(days: 7));
|
||||
case 'monthly':
|
||||
return DateTime(dateTime.year, dateTime.month + 1, dateTime.day);
|
||||
case 'custom':
|
||||
// Find next weekday in repeatDays
|
||||
var next = dateTime.add(const Duration(days: 1));
|
||||
while (!repeatDays.contains(next.weekday)) {
|
||||
next = next.add(const Duration(days: 1));
|
||||
}
|
||||
return DateTime(next.year, next.month, next.day, dateTime.hour, dateTime.minute);
|
||||
default:
|
||||
return dateTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
|
||||
class CalendarEventModel {
|
||||
final String id;
|
||||
final String title;
|
||||
final String? description;
|
||||
final String location;
|
||||
final DateTime startTime;
|
||||
final DateTime endTime;
|
||||
final bool isAllDay;
|
||||
final String? googleEventId;
|
||||
final String? taskId;
|
||||
final List<String> attendeeIds;
|
||||
final List<String> attendeeEmails;
|
||||
final String color;
|
||||
final String? recurringRuleId;
|
||||
final Map<String, dynamic> reminders;
|
||||
final String status; // confirmed, tentative, cancelled
|
||||
final String createdBy;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
|
||||
CalendarEventModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.description,
|
||||
this.location = '',
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
this.isAllDay = false,
|
||||
this.googleEventId,
|
||||
this.taskId,
|
||||
this.attendeeIds = const [],
|
||||
this.attendeeEmails = const [],
|
||||
this.color = '#2196F3',
|
||||
this.recurringRuleId,
|
||||
this.reminders = const {},
|
||||
this.status = 'confirmed',
|
||||
required this.createdBy,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
factory CalendarEventModel.fromFirestore(DocumentSnapshot doc) {
|
||||
final data = doc.data() as Map<String, dynamic>;
|
||||
return CalendarEventModel(
|
||||
id: doc.id,
|
||||
title: data['title'] ?? '',
|
||||
description: data['description'],
|
||||
location: data['location'] ?? '',
|
||||
startTime: (data['startTime'] as Timestamp).toDate(),
|
||||
endTime: (data['endTime'] as Timestamp).toDate(),
|
||||
isAllDay: data['isAllDay'] ?? false,
|
||||
googleEventId: data['googleEventId'],
|
||||
taskId: data['taskId'],
|
||||
attendeeIds: List<String>.from(data['attendeeIds'] ?? []),
|
||||
attendeeEmails: List<String>.from(data['attendeeEmails'] ?? []),
|
||||
color: data['color'] ?? '#2196F3',
|
||||
recurringRuleId: data['recurringRuleId'],
|
||||
reminders: data['reminders'] ?? {},
|
||||
status: data['status'] ?? 'confirmed',
|
||||
createdBy: data['createdBy'] ?? '',
|
||||
createdAt: (data['createdAt'] as Timestamp).toDate(),
|
||||
updatedAt: (data['updatedAt'] as Timestamp).toDate(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toFirestore() {
|
||||
return {
|
||||
'title': title,
|
||||
'description': description,
|
||||
'location': location,
|
||||
'startTime': Timestamp.fromDate(startTime),
|
||||
'endTime': Timestamp.fromDate(endTime),
|
||||
'isAllDay': isAllDay,
|
||||
'googleEventId': googleEventId,
|
||||
'taskId': taskId,
|
||||
'attendeeIds': attendeeIds,
|
||||
'attendeeEmails': attendeeEmails,
|
||||
'color': color,
|
||||
'recurringRuleId': recurringRuleId,
|
||||
'reminders': reminders,
|
||||
'status': status,
|
||||
'createdBy': createdBy,
|
||||
'createdAt': Timestamp.fromDate(createdAt),
|
||||
'updatedAt': Timestamp.fromDate(updatedAt),
|
||||
};
|
||||
}
|
||||
|
||||
CalendarEventModel copyWith({
|
||||
String? id,
|
||||
String? title,
|
||||
String? description,
|
||||
String? location,
|
||||
DateTime? startTime,
|
||||
DateTime? endTime,
|
||||
bool? isAllDay,
|
||||
String? googleEventId,
|
||||
String? taskId,
|
||||
List<String>? attendeeIds,
|
||||
List<String>? attendeeEmails,
|
||||
String? color,
|
||||
String? recurringRuleId,
|
||||
Map<String, dynamic>? reminders,
|
||||
String? status,
|
||||
String? createdBy,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
}) {
|
||||
return CalendarEventModel(
|
||||
id: id ?? this.id,
|
||||
title: title ?? this.title,
|
||||
description: description ?? this.description,
|
||||
location: location ?? this.location,
|
||||
startTime: startTime ?? this.startTime,
|
||||
endTime: endTime ?? this.endTime,
|
||||
isAllDay: isAllDay ?? this.isAllDay,
|
||||
googleEventId: googleEventId ?? this.googleEventId,
|
||||
taskId: taskId ?? this.taskId,
|
||||
attendeeIds: attendeeIds ?? this.attendeeIds,
|
||||
attendeeEmails: attendeeEmails ?? this.attendeeEmails,
|
||||
color: color ?? this.color,
|
||||
recurringRuleId: recurringRuleId ?? this.recurringRuleId,
|
||||
reminders: reminders ?? this.reminders,
|
||||
status: status ?? this.status,
|
||||
createdBy: createdBy ?? this.createdBy,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
);
|
||||
}
|
||||
|
||||
Duration get duration => endTime.difference(startTime);
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
|
||||
class MindMapNode {
|
||||
final String id;
|
||||
final String projectId;
|
||||
final String? parentId;
|
||||
final int level;
|
||||
final String title;
|
||||
final String? description;
|
||||
final String? taskId; // Link to task
|
||||
final String color;
|
||||
final String icon;
|
||||
final Map<String, dynamic> metadata;
|
||||
final double x;
|
||||
final double y;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
|
||||
MindMapNode({
|
||||
required this.id,
|
||||
required this.projectId,
|
||||
this.parentId,
|
||||
required this.level,
|
||||
required this.title,
|
||||
this.description,
|
||||
this.taskId,
|
||||
this.color = '#2196F3',
|
||||
this.icon = 'folder',
|
||||
this.metadata = const {},
|
||||
this.x = 0,
|
||||
this.y = 0,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
factory MindMapNode.fromFirestore(DocumentSnapshot doc) {
|
||||
final data = doc.data() as Map<String, dynamic>;
|
||||
return MindMapNode(
|
||||
id: doc.id,
|
||||
projectId: data['projectId'] ?? '',
|
||||
parentId: data['parentId'],
|
||||
level: data['level'] ?? 0,
|
||||
title: data['title'] ?? '',
|
||||
description: data['description'],
|
||||
taskId: data['taskId'],
|
||||
color: data['color'] ?? '#2196F3',
|
||||
icon: data['icon'] ?? 'folder',
|
||||
metadata: data['metadata'] ?? {},
|
||||
x: (data['x'] ?? 0).toDouble(),
|
||||
y: (data['y'] ?? 0).toDouble(),
|
||||
createdAt: (data['createdAt'] as Timestamp).toDate(),
|
||||
updatedAt: (data['updatedAt'] as Timestamp).toDate(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toFirestore() {
|
||||
return {
|
||||
'projectId': projectId,
|
||||
'parentId': parentId,
|
||||
'level': level,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'taskId': taskId,
|
||||
'color': color,
|
||||
'icon': icon,
|
||||
'metadata': metadata,
|
||||
'x': x,
|
||||
'y': y,
|
||||
'createdAt': Timestamp.fromDate(createdAt),
|
||||
'updatedAt': Timestamp.fromDate(updatedAt),
|
||||
};
|
||||
}
|
||||
|
||||
MindMapNode copyWith({
|
||||
String? id,
|
||||
String? projectId,
|
||||
String? parentId,
|
||||
int? level,
|
||||
String? title,
|
||||
String? description,
|
||||
String? taskId,
|
||||
String? color,
|
||||
String? icon,
|
||||
Map<String, dynamic>? metadata,
|
||||
double? x,
|
||||
double? y,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
}) {
|
||||
return MindMapNode(
|
||||
id: id ?? this.id,
|
||||
projectId: projectId ?? this.projectId,
|
||||
parentId: parentId ?? this.parentId,
|
||||
level: level ?? this.level,
|
||||
title: title ?? this.title,
|
||||
description: description ?? this.description,
|
||||
taskId: taskId ?? this.taskId,
|
||||
color: color ?? this.color,
|
||||
icon: icon ?? this.icon,
|
||||
metadata: metadata ?? this.metadata,
|
||||
x: x ?? this.x,
|
||||
y: y ?? this.y,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MindMapModel {
|
||||
final String id;
|
||||
final String projectId;
|
||||
final String title;
|
||||
final String description;
|
||||
final String rootNodeId;
|
||||
final List<MindMapNode> nodes;
|
||||
final Map<String, dynamic> layout;
|
||||
final String createdBy;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
|
||||
MindMapModel({
|
||||
required this.id,
|
||||
required this.projectId,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.rootNodeId,
|
||||
required this.nodes,
|
||||
required this.layout,
|
||||
required this.createdBy,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
factory MindMapModel.fromFirestore({
|
||||
required String id,
|
||||
required Map<String, dynamic> data,
|
||||
required List<MindMapNode> nodes,
|
||||
}) {
|
||||
return MindMapModel(
|
||||
id: id,
|
||||
projectId: data['projectId'] ?? '',
|
||||
title: data['title'] ?? '',
|
||||
description: data['description'] ?? '',
|
||||
rootNodeId: data['rootNodeId'] ?? '',
|
||||
nodes: nodes,
|
||||
layout: data['layout'] ?? {},
|
||||
createdBy: data['createdBy'] ?? '',
|
||||
createdAt: (data['createdAt'] as Timestamp).toDate(),
|
||||
updatedAt: (data['updatedAt'] as Timestamp).toDate(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toFirestore() {
|
||||
return {
|
||||
'projectId': projectId,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'rootNodeId': rootNodeId,
|
||||
'layout': layout,
|
||||
'createdBy': createdBy,
|
||||
'createdAt': Timestamp.fromDate(createdAt),
|
||||
'updatedAt': Timestamp.fromDate(updatedAt),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
|
||||
class ProjectModel {
|
||||
final String id;
|
||||
final String name;
|
||||
final String description;
|
||||
final String teamId;
|
||||
final String ownerId;
|
||||
final List<String> memberIds;
|
||||
final String status;
|
||||
final DateTime startDate;
|
||||
final DateTime? endDate;
|
||||
final String color;
|
||||
final String icon;
|
||||
final Map<String, dynamic> settings;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
|
||||
ProjectModel({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.teamId,
|
||||
required this.ownerId,
|
||||
this.memberIds = const [],
|
||||
this.status = 'active',
|
||||
required this.startDate,
|
||||
this.endDate,
|
||||
this.color = '#1976D2',
|
||||
this.icon = 'project',
|
||||
this.settings = const {},
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
factory ProjectModel.fromFirestore(DocumentSnapshot doc) {
|
||||
final data = doc.data() as Map<String, dynamic>;
|
||||
return ProjectModel(
|
||||
id: doc.id,
|
||||
name: data['name'] ?? '',
|
||||
description: data['description'] ?? '',
|
||||
teamId: data['teamId'] ?? '',
|
||||
ownerId: data['ownerId'] ?? '',
|
||||
memberIds: List<String>.from(data['memberIds'] ?? []),
|
||||
status: data['status'] ?? 'active',
|
||||
startDate: (data['startDate'] as Timestamp).toDate(),
|
||||
endDate: data['endDate'] != null
|
||||
? (data['endDate'] as Timestamp).toDate()
|
||||
: null,
|
||||
color: data['color'] ?? '#1976D2',
|
||||
icon: data['icon'] ?? 'project',
|
||||
settings: data['settings'] ?? {},
|
||||
createdAt: (data['createdAt'] as Timestamp).toDate(),
|
||||
updatedAt: (data['updatedAt'] as Timestamp).toDate(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toFirestore() {
|
||||
return {
|
||||
'name': name,
|
||||
'description': description,
|
||||
'teamId': teamId,
|
||||
'ownerId': ownerId,
|
||||
'memberIds': memberIds,
|
||||
'status': status,
|
||||
'startDate': Timestamp.fromDate(startDate),
|
||||
'endDate': endDate != null ? Timestamp.fromDate(endDate!) : null,
|
||||
'color': color,
|
||||
'icon': icon,
|
||||
'settings': settings,
|
||||
'createdAt': Timestamp.fromDate(createdAt),
|
||||
'updatedAt': Timestamp.fromDate(updatedAt),
|
||||
};
|
||||
}
|
||||
|
||||
ProjectModel copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
String? description,
|
||||
String? teamId,
|
||||
String? ownerId,
|
||||
List<String>? memberIds,
|
||||
String? status,
|
||||
DateTime? startDate,
|
||||
DateTime? endDate,
|
||||
String? color,
|
||||
String? icon,
|
||||
Map<String, dynamic>? settings,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
}) {
|
||||
return ProjectModel(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
description: description ?? this.description,
|
||||
teamId: teamId ?? this.teamId,
|
||||
ownerId: ownerId ?? this.ownerId,
|
||||
memberIds: memberIds ?? this.memberIds,
|
||||
status: status ?? this.status,
|
||||
startDate: startDate ?? this.startDate,
|
||||
endDate: endDate ?? this.endDate,
|
||||
color: color ?? this.color,
|
||||
icon: icon ?? this.icon,
|
||||
settings: settings ?? this.settings,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
);
|
||||
}
|
||||
|
||||
bool isMember(String userId) {
|
||||
return ownerId == userId || memberIds.contains(userId);
|
||||
}
|
||||
|
||||
bool isOwner(String userId) {
|
||||
return ownerId == userId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
|
||||
enum TaskStatus {
|
||||
todo,
|
||||
inProgress,
|
||||
review,
|
||||
done,
|
||||
archived,
|
||||
}
|
||||
|
||||
enum TaskPriority {
|
||||
low,
|
||||
medium,
|
||||
high,
|
||||
urgent,
|
||||
}
|
||||
|
||||
class TaskModel {
|
||||
final String id;
|
||||
final String title;
|
||||
final String? description;
|
||||
final String projectId;
|
||||
final String? parentTaskId; // For mind-map hierarchy
|
||||
final TaskStatus status;
|
||||
final TaskPriority priority;
|
||||
final String? assigneeId;
|
||||
final String? creatorId;
|
||||
final List<String> tags;
|
||||
final DateTime? dueDate;
|
||||
final DateTime? startDate;
|
||||
final DateTime? completedDate;
|
||||
final int estimatedHours;
|
||||
final int spentHours;
|
||||
final Map<String, dynamic> aiMetadata; // AI-generated suggestions
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
|
||||
TaskModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.description,
|
||||
required this.projectId,
|
||||
this.parentTaskId,
|
||||
this.status = TaskStatus.todo,
|
||||
this.priority = TaskPriority.medium,
|
||||
this.assigneeId,
|
||||
this.creatorId,
|
||||
this.tags = const [],
|
||||
this.dueDate,
|
||||
this.startDate,
|
||||
this.completedDate,
|
||||
this.estimatedHours = 0,
|
||||
this.spentHours = 0,
|
||||
this.aiMetadata = const {},
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
factory TaskModel.fromFirestore(DocumentSnapshot doc) {
|
||||
final data = doc.data() as Map<String, dynamic>;
|
||||
return TaskModel(
|
||||
id: doc.id,
|
||||
title: data['title'] ?? '',
|
||||
description: data['description'],
|
||||
projectId: data['projectId'] ?? '',
|
||||
parentTaskId: data['parentTaskId'],
|
||||
status: TaskStatus.values.firstWhere(
|
||||
(e) => e.name == data['status'],
|
||||
orElse: () => TaskStatus.todo,
|
||||
),
|
||||
priority: TaskPriority.values.firstWhere(
|
||||
(e) => e.name == data['priority'],
|
||||
orElse: () => TaskPriority.medium,
|
||||
),
|
||||
assigneeId: data['assigneeId'],
|
||||
creatorId: data['creatorId'],
|
||||
tags: List<String>.from(data['tags'] ?? []),
|
||||
dueDate: data['dueDate'] != null
|
||||
? (data['dueDate'] as Timestamp).toDate()
|
||||
: null,
|
||||
startDate: data['startDate'] != null
|
||||
? (data['startDate'] as Timestamp).toDate()
|
||||
: null,
|
||||
completedDate: data['completedDate'] != null
|
||||
? (data['completedDate'] as Timestamp).toDate()
|
||||
: null,
|
||||
estimatedHours: data['estimatedHours'] ?? 0,
|
||||
spentHours: data['spentHours'] ?? 0,
|
||||
aiMetadata: data['aiMetadata'] ?? {},
|
||||
createdAt: (data['createdAt'] as Timestamp).toDate(),
|
||||
updatedAt: (data['updatedAt'] as Timestamp).toDate(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toFirestore() {
|
||||
return {
|
||||
'title': title,
|
||||
'description': description,
|
||||
'projectId': projectId,
|
||||
'parentTaskId': parentTaskId,
|
||||
'status': status.name,
|
||||
'priority': priority.name,
|
||||
'assigneeId': assigneeId,
|
||||
'creatorId': creatorId,
|
||||
'tags': tags,
|
||||
'dueDate': dueDate != null ? Timestamp.fromDate(dueDate!) : null,
|
||||
'startDate': startDate != null ? Timestamp.fromDate(startDate!) : null,
|
||||
'completedDate': completedDate != null
|
||||
? Timestamp.fromDate(completedDate!)
|
||||
: null,
|
||||
'estimatedHours': estimatedHours,
|
||||
'spentHours': spentHours,
|
||||
'aiMetadata': aiMetadata,
|
||||
'createdAt': Timestamp.fromDate(createdAt),
|
||||
'updatedAt': Timestamp.fromDate(updatedAt),
|
||||
};
|
||||
}
|
||||
|
||||
TaskModel copyWith({
|
||||
String? id,
|
||||
String? title,
|
||||
String? description,
|
||||
String? projectId,
|
||||
String? parentTaskId,
|
||||
TaskStatus? status,
|
||||
TaskPriority? priority,
|
||||
String? assigneeId,
|
||||
String? creatorId,
|
||||
List<String>? tags,
|
||||
DateTime? dueDate,
|
||||
DateTime? startDate,
|
||||
DateTime? completedDate,
|
||||
int? estimatedHours,
|
||||
int? spentHours,
|
||||
Map<String, dynamic>? aiMetadata,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
}) {
|
||||
return TaskModel(
|
||||
id: id ?? this.id,
|
||||
title: title ?? this.title,
|
||||
description: description ?? this.description,
|
||||
projectId: projectId ?? this.projectId,
|
||||
parentTaskId: parentTaskId ?? this.parentTaskId,
|
||||
status: status ?? this.status,
|
||||
priority: priority ?? this.priority,
|
||||
assigneeId: assigneeId ?? this.assigneeId,
|
||||
creatorId: creatorId ?? this.creatorId,
|
||||
tags: tags ?? this.tags,
|
||||
dueDate: dueDate ?? this.dueDate,
|
||||
startDate: startDate ?? this.startDate,
|
||||
completedDate: completedDate ?? this.completedDate,
|
||||
estimatedHours: estimatedHours ?? this.estimatedHours,
|
||||
spentHours: spentHours ?? this.spentHours,
|
||||
aiMetadata: aiMetadata ?? this.aiMetadata,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
);
|
||||
}
|
||||
|
||||
bool get isOverdue {
|
||||
if (dueDate == null) return false;
|
||||
return DateTime.now().isAfter(dueDate!) && status != TaskStatus.done;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
|
||||
enum UserRole {
|
||||
admin,
|
||||
member,
|
||||
viewer,
|
||||
}
|
||||
|
||||
class UserModel {
|
||||
final String id;
|
||||
final String email;
|
||||
final String? displayName;
|
||||
final String? photoUrl;
|
||||
final UserRole role;
|
||||
final List<String> teamIds;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
final Map<String, dynamic> settings;
|
||||
|
||||
UserModel({
|
||||
required this.id,
|
||||
required this.email,
|
||||
this.displayName,
|
||||
this.photoUrl,
|
||||
this.role = UserRole.member,
|
||||
this.teamIds = const [],
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.settings = const {},
|
||||
});
|
||||
|
||||
factory UserModel.fromFirestore(DocumentSnapshot doc) {
|
||||
final data = doc.data() as Map<String, dynamic>;
|
||||
return UserModel(
|
||||
id: doc.id,
|
||||
email: data['email'] ?? '',
|
||||
displayName: data['displayName'],
|
||||
photoUrl: data['photoUrl'],
|
||||
role: UserRole.values.firstWhere(
|
||||
(e) => e.name == data['role'],
|
||||
orElse: () => UserRole.member,
|
||||
),
|
||||
teamIds: List<String>.from(data['teamIds'] ?? []),
|
||||
createdAt: (data['createdAt'] as Timestamp).toDate(),
|
||||
updatedAt: (data['updatedAt'] as Timestamp).toDate(),
|
||||
settings: data['settings'] ?? {},
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toFirestore() {
|
||||
return {
|
||||
'email': email,
|
||||
'displayName': displayName,
|
||||
'photoUrl': photoUrl,
|
||||
'role': role.name,
|
||||
'teamIds': teamIds,
|
||||
'createdAt': Timestamp.fromDate(createdAt),
|
||||
'updatedAt': Timestamp.fromDate(updatedAt),
|
||||
'settings': settings,
|
||||
};
|
||||
}
|
||||
|
||||
UserModel copyWith({
|
||||
String? id,
|
||||
String? email,
|
||||
String? displayName,
|
||||
String? photoUrl,
|
||||
UserRole? role,
|
||||
List<String>? teamIds,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
Map<String, dynamic>? settings,
|
||||
}) {
|
||||
return UserModel(
|
||||
id: id ?? this.id,
|
||||
email: email ?? this.email,
|
||||
displayName: displayName ?? this.displayName,
|
||||
photoUrl: photoUrl ?? this.photoUrl,
|
||||
role: role ?? this.role,
|
||||
teamIds: teamIds ?? this.teamIds,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
settings: settings ?? this.settings,
|
||||
);
|
||||
}
|
||||
|
||||
bool get isAdmin => role == UserRole.admin;
|
||||
bool get isMember => role == UserRole.member;
|
||||
bool get isViewer => role == UserRole.viewer;
|
||||
}
|
||||
Reference in New Issue
Block a user