Initial commit: NANAHUI CRM - Flutter Firebase CRM assistant

This commit is contained in:
2026-03-27 14:38:10 +05:00
commit 78d15daa35
161 changed files with 9924 additions and 0 deletions
+132
View File
@@ -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;
}
}
}
+133
View File
@@ -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);
}
+165
View File
@@ -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),
};
}
}
+117
View File
@@ -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;
}
}
+165
View File
@@ -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;
}
}
+90
View File
@@ -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;
}