331 lines
10 KiB
Dart
331 lines
10 KiB
Dart
/**
|
|
* ==============================================================================
|
|
* SAQEL TEACHER STUDIO - DATA MODELS
|
|
* ==============================================================================
|
|
*
|
|
* نماذج البيانات الخاصة باستوديو المعلم:
|
|
* - نتائج فحص الجودة الإدراكي والأكاديمي (Quality Gate & MIT 20-25 Min Limit)
|
|
* - بيانات التسييل المالي وحسابات الحصص (55% المعلم / 15% المديرية / 30% المنصة)
|
|
* - أوراق العمل والواجبات المدرسية المصححة آلياً
|
|
* - استفسارات وأسئلة الطلبة الميدانية (غرفة الشكوك) والردود السقراطية المسجلة.
|
|
*/
|
|
|
|
class TeacherLessonAuditModel {
|
|
final String lessonTitle;
|
|
final String subject;
|
|
final double durationMinutes;
|
|
final bool durationGatePassed;
|
|
final String? durationWarning;
|
|
final int qualityScore;
|
|
final String approvalStatus;
|
|
final String curriculumAlignment;
|
|
final String audioClarity;
|
|
final int socraticStopsCount;
|
|
final String decision;
|
|
|
|
const TeacherLessonAuditModel({
|
|
required this.lessonTitle,
|
|
required this.subject,
|
|
required this.durationMinutes,
|
|
required this.durationGatePassed,
|
|
this.durationWarning,
|
|
required this.qualityScore,
|
|
required this.approvalStatus,
|
|
required this.curriculumAlignment,
|
|
required this.audioClarity,
|
|
required this.socraticStopsCount,
|
|
required this.decision,
|
|
});
|
|
|
|
factory TeacherLessonAuditModel.fromJson(Map<String, dynamic> json) {
|
|
final durationSeconds = (json['duration_seconds'] as num?)?.toDouble() ?? 0;
|
|
final readiness = (json['pedagogical_readiness_score'] as num?)?.toInt();
|
|
final visual = (json['visual_clarity_score'] as num?)?.toInt();
|
|
final decisionValue = json['decision']?.toString() ?? 'pending';
|
|
|
|
final ai = json['ai_analysis'] is Map ? Map<String, dynamic>.from(json['ai_analysis'] as Map) : null;
|
|
final qa = ai != null && ai['quality_assessment'] is Map ? Map<String, dynamic>.from(ai['quality_assessment'] as Map) : null;
|
|
final cpCount = (ai?['checkpoints_count'] as num?)?.toInt() ?? (json['socratic_stops_count'] as num?)?.toInt() ?? 0;
|
|
final alignScore = (qa?['alignment_score'] as num?)?.toInt();
|
|
|
|
return TeacherLessonAuditModel(
|
|
lessonTitle: json['lesson_title'] ?? '',
|
|
subject: json['subject'] ?? '',
|
|
durationMinutes: (json['duration_minutes'] as num?)?.toDouble() ??
|
|
durationSeconds / 60,
|
|
durationGatePassed: json['duration_gate_passed'] ??
|
|
(durationSeconds > 0 && durationSeconds <= 1500),
|
|
durationWarning: json['duration_warning'],
|
|
qualityScore: (qa?['clarity_score'] as num?)?.toInt() ?? (json['quality_score'] as num?)?.toInt() ?? readiness ?? 0,
|
|
approvalStatus: qa?['status']?.toString() ?? json['approval_status'] ?? decisionValue,
|
|
curriculumAlignment: alignScore != null
|
|
? 'تطابق معتمد بنسبة $alignScore% مع المنهاج الرسمي'
|
|
: (json['curriculum_alignment'] ?? 'بانتظار تحليل الملف الفعلي'),
|
|
audioClarity: json['audio_clarity'] ??
|
|
(visual == null
|
|
? 'تم التحقق من وجود مسار صوت فعلي'
|
|
: 'وضوح الصورة $visual%، وتم التحقق من وجود الصوت'),
|
|
socraticStopsCount: cpCount,
|
|
decision: json['report'] ?? json['reason'] ?? decisionValue,
|
|
);
|
|
}
|
|
}
|
|
|
|
class TeacherHomeworkModel {
|
|
final String id;
|
|
final String title;
|
|
final String className;
|
|
final String submitted;
|
|
final String dueDate;
|
|
final String averageScore;
|
|
final String status; // 'active', 'completed'
|
|
|
|
const TeacherHomeworkModel({
|
|
required this.id,
|
|
required this.title,
|
|
required this.className,
|
|
required this.submitted,
|
|
required this.dueDate,
|
|
required this.averageScore,
|
|
required this.status,
|
|
});
|
|
}
|
|
|
|
class StudentDoubtModel {
|
|
final String id;
|
|
final int studentId;
|
|
final String studentName;
|
|
final String className;
|
|
final String question;
|
|
final String time;
|
|
final bool replied;
|
|
final bool voiceReply;
|
|
|
|
const StudentDoubtModel({
|
|
required this.id,
|
|
required this.studentId,
|
|
required this.studentName,
|
|
required this.className,
|
|
required this.question,
|
|
required this.time,
|
|
required this.replied,
|
|
required this.voiceReply,
|
|
});
|
|
|
|
StudentDoubtModel copyWith({
|
|
bool? replied,
|
|
bool? voiceReply,
|
|
}) {
|
|
return StudentDoubtModel(
|
|
id: id,
|
|
studentId: studentId,
|
|
studentName: studentName,
|
|
className: className,
|
|
question: question,
|
|
time: time,
|
|
replied: replied ?? this.replied,
|
|
voiceReply: voiceReply ?? this.voiceReply,
|
|
);
|
|
}
|
|
}
|
|
|
|
class TeacherCourseModel {
|
|
final String id;
|
|
final String title;
|
|
final String grade;
|
|
final int totalLessons;
|
|
final int militaryStudents;
|
|
final int externalSubscribers;
|
|
final double priceJod;
|
|
final double netRevenueJod;
|
|
|
|
const TeacherCourseModel({
|
|
required this.id,
|
|
required this.title,
|
|
required this.grade,
|
|
required this.totalLessons,
|
|
required this.militaryStudents,
|
|
required this.externalSubscribers,
|
|
required this.priceJod,
|
|
required this.netRevenueJod,
|
|
});
|
|
}
|
|
|
|
class TeacherPayoutRequestModel {
|
|
final String cliqAlias;
|
|
final double amountJod;
|
|
final String requestedAt;
|
|
final String status; // 'queued', 'approved', 'completed'
|
|
|
|
const TeacherPayoutRequestModel({
|
|
required this.cliqAlias,
|
|
required this.amountJod,
|
|
required this.requestedAt,
|
|
required this.status,
|
|
});
|
|
}
|
|
|
|
class TeacherProfileModel {
|
|
final int id;
|
|
final String uuid;
|
|
final String name;
|
|
final String specialization;
|
|
final String schoolName;
|
|
final List<String> gradesTaught;
|
|
final String bio;
|
|
final double rating;
|
|
final int studentsCount;
|
|
final int lessonsCount;
|
|
|
|
const TeacherProfileModel({
|
|
required this.id,
|
|
required this.uuid,
|
|
required this.name,
|
|
required this.specialization,
|
|
required this.schoolName,
|
|
required this.gradesTaught,
|
|
required this.bio,
|
|
this.rating = 4.9,
|
|
this.studentsCount = 42,
|
|
this.lessonsCount = 28,
|
|
});
|
|
|
|
factory TeacherProfileModel.fromJson(Map<String, dynamic> json) {
|
|
final user = json['user'] as Map<String, dynamic>? ?? {};
|
|
final profile = json['profile'] as Map<String, dynamic>? ?? {};
|
|
|
|
List<String> grades = [];
|
|
if (profile['grades_taught'] is List) {
|
|
grades =
|
|
(profile['grades_taught'] as List).map((e) => e.toString()).toList();
|
|
} else if (profile['grades_taught'] is String) {
|
|
grades = [profile['grades_taught'].toString()];
|
|
}
|
|
if (grades.isEmpty) {
|
|
grades = [];
|
|
}
|
|
|
|
return TeacherProfileModel(
|
|
id: user['id'] is int ? user['id'] : 1,
|
|
uuid: user['uuid']?.toString() ?? '',
|
|
name: user['full_name']?.toString() ?? 'حساب معلم غير مكتمل',
|
|
specialization:
|
|
profile['specialization']?.toString() ?? 'بانتظار استكمال التخصص',
|
|
schoolName: profile['school_name']?.toString() ?? '',
|
|
gradesTaught: grades,
|
|
bio: profile['bio']?.toString() ?? '',
|
|
rating: 0,
|
|
studentsCount: 0,
|
|
lessonsCount: 0,
|
|
);
|
|
}
|
|
|
|
TeacherProfileModel copyWith({
|
|
String? name,
|
|
String? specialization,
|
|
String? schoolName,
|
|
List<String>? gradesTaught,
|
|
String? bio,
|
|
}) {
|
|
return TeacherProfileModel(
|
|
id: id,
|
|
uuid: uuid,
|
|
name: name ?? this.name,
|
|
specialization: specialization ?? this.specialization,
|
|
schoolName: schoolName ?? this.schoolName,
|
|
gradesTaught: gradesTaught ?? this.gradesTaught,
|
|
bio: bio ?? this.bio,
|
|
rating: rating,
|
|
studentsCount: studentsCount,
|
|
lessonsCount: lessonsCount,
|
|
);
|
|
}
|
|
}
|
|
|
|
class ChatConversationModel {
|
|
final int userId;
|
|
final String userUuid;
|
|
final String fullName;
|
|
final String role;
|
|
final String? specialization;
|
|
final String lastMessage;
|
|
final String lastMessageType;
|
|
final String? lastMessageAt;
|
|
final int unreadCount;
|
|
|
|
const ChatConversationModel({
|
|
required this.userId,
|
|
required this.userUuid,
|
|
required this.fullName,
|
|
required this.role,
|
|
this.specialization,
|
|
required this.lastMessage,
|
|
required this.lastMessageType,
|
|
this.lastMessageAt,
|
|
this.unreadCount = 0,
|
|
});
|
|
|
|
factory ChatConversationModel.fromJson(Map<String, dynamic> json) {
|
|
return ChatConversationModel(
|
|
userId: json['user_id'] is int
|
|
? json['user_id']
|
|
: int.tryParse(json['user_id']?.toString() ?? '0') ?? 0,
|
|
userUuid: json['user_uuid']?.toString() ?? '',
|
|
fullName: json['full_name']?.toString() ?? 'طالب صَقِل',
|
|
role: json['role']?.toString() ?? 'student',
|
|
specialization: json['specialization']?.toString(),
|
|
lastMessage: json['last_message']?.toString() ?? '',
|
|
lastMessageType: json['last_message_type']?.toString() ?? 'text',
|
|
lastMessageAt: json['last_message_at']?.toString(),
|
|
unreadCount: json['unread_count'] is int ? json['unread_count'] : 0,
|
|
);
|
|
}
|
|
}
|
|
|
|
class ChatMessageModel {
|
|
final int id;
|
|
final String uuid;
|
|
final bool isMine;
|
|
final int senderId;
|
|
final int receiverId;
|
|
final String message;
|
|
final String messageType; // 'text', 'voice', 'image', 'file'
|
|
final String? mediaUrl;
|
|
final bool isRead;
|
|
final String createdAt;
|
|
|
|
const ChatMessageModel({
|
|
required this.id,
|
|
required this.uuid,
|
|
required this.isMine,
|
|
required this.senderId,
|
|
required this.receiverId,
|
|
required this.message,
|
|
required this.messageType,
|
|
this.mediaUrl,
|
|
required this.isRead,
|
|
required this.createdAt,
|
|
});
|
|
|
|
factory ChatMessageModel.fromJson(Map<String, dynamic> json) {
|
|
return ChatMessageModel(
|
|
id: json['id'] is int
|
|
? json['id']
|
|
: int.tryParse(json['id']?.toString() ?? '0') ?? 0,
|
|
uuid: json['uuid']?.toString() ?? '',
|
|
isMine: json['is_mine'] ?? false,
|
|
senderId: json['sender_id'] is int
|
|
? json['sender_id']
|
|
: int.tryParse(json['sender_id']?.toString() ?? '0') ?? 0,
|
|
receiverId: json['receiver_id'] is int
|
|
? json['receiver_id']
|
|
: int.tryParse(json['receiver_id']?.toString() ?? '0') ?? 0,
|
|
message: json['message']?.toString() ?? '',
|
|
messageType: json['message_type']?.toString() ?? 'text',
|
|
mediaUrl: json['media_url']?.toString(),
|
|
isRead: json['is_read'] ?? false,
|
|
createdAt: json['created_at']?.toString() ?? '',
|
|
);
|
|
}
|
|
}
|