EGYADMIN commited on
Commit
4624a18
·
verified ·
1 Parent(s): d7e0640

Upload 34 files

Browse files
modules/risk_analysis/__pycache__/risk_analyzer.cpython-310.pyc ADDED
Binary file (33.1 kB). View file
 
modules/risk_analysis/risk_analyzer.py CHANGED
@@ -8,7 +8,18 @@ import threading
8
  import datetime
9
  import json
10
  import math
 
11
  from pathlib import Path
 
 
 
 
 
 
 
 
 
 
12
 
13
  # تهيئة السجل
14
  logging.basicConfig(
@@ -244,9 +255,9 @@ class RiskAnalyzer:
244
  additional_risks.extend([
245
  {
246
  "id": 401,
247
- "name": "مخاطر الأمن السيبراني",
248
- "description": "مخاطر الهجمات الإلكترونية على أنظمة إدارة المشروع",
249
- "category": "تقني",
250
  "probability": "منخفض",
251
  "impact": "عالي",
252
  "risk_score": self._calculate_risk_score("منخفض", "عالي"),
@@ -254,19 +265,19 @@ class RiskAnalyzer:
254
  },
255
  {
256
  "id": 402,
257
- "name": "تغيرات في اللوائح والأنظمة",
258
- "description": "تغييرات في اللوائح والأنظمة الحكومية المتعلقة بالمشروع",
259
- "category": "تنظيمي",
260
- "probability": "منخفض",
261
  "impact": "متوسط",
262
- "risk_score": self._calculate_risk_score("منخفض", "متوسط"),
263
  "source": "generated"
264
  },
265
  {
266
  "id": 403,
267
- "name": "مخاطر العملة والتضخم",
268
- "description": "تقلبات أسعار العملة ومعدلات التضخم",
269
- "category": "مالي",
270
  "probability": "متوسط",
271
  "impact": "متوسط",
272
  "risk_score": self._calculate_risk_score("متوسط", "متوسط"),
@@ -278,22 +289,15 @@ class RiskAnalyzer:
278
 
279
  def _calculate_risk_score(self, probability, impact):
280
  """حساب درجة المخاطرة"""
281
- probability_map = {
282
- "منخفض": 1,
283
- "متوسط": 2,
284
- "عالي": 3
285
- }
286
 
287
- impact_map = {
288
- "منخفض": 1,
289
- "متوسط": 2,
290
- "عالي": 3
291
- }
292
 
293
- prob_value = probability_map.get(probability, 1)
294
- impact_value = impact_map.get(impact, 1)
295
-
296
- return prob_value * impact_value
297
 
298
  def _categorize_risks(self):
299
  """تصنيف المخاطر"""
@@ -301,51 +305,33 @@ class RiskAnalyzer:
301
 
302
  for risk in self.analysis_results["identified_risks"]:
303
  category = risk["category"]
304
-
305
  if category not in categories:
306
- categories[category] = {
307
- "count": 0,
308
- "risks": [],
309
- "avg_score": 0,
310
- "max_score": 0
311
- }
312
 
313
- categories[category]["count"] += 1
314
- categories[category]["risks"].append(risk)
315
- categories[category]["max_score"] = max(categories[category]["max_score"], risk["risk_score"])
316
 
317
- # حساب متوسط درجة المخاطرة لكل فئة
318
- for category in categories:
319
- total_score = sum(risk["risk_score"] for risk in categories[category]["risks"])
320
- categories[category]["avg_score"] = total_score / categories[category]["count"]
321
-
322
- # ترتيب الفئات حسب متوسط درجة المخاطرة
323
- sorted_categories = dict(sorted(
324
- categories.items(),
325
- key=lambda item: item[1]["avg_score"],
326
- reverse=True
327
- ))
 
 
328
 
329
- self.analysis_results["risk_categories"] = sorted_categories
330
 
331
  def _create_risk_matrix(self):
332
  """إنشاء مصفوفة المخاطر"""
333
  matrix = {
334
- "high_impact": {
335
- "high_probability": [],
336
- "medium_probability": [],
337
- "low_probability": []
338
- },
339
- "medium_impact": {
340
- "high_probability": [],
341
- "medium_probability": [],
342
- "low_probability": []
343
- },
344
- "low_impact": {
345
- "high_probability": [],
346
- "medium_probability": [],
347
- "low_probability": []
348
- }
349
  }
350
 
351
  for risk in self.analysis_results["identified_risks"]:
@@ -353,233 +339,816 @@ class RiskAnalyzer:
353
  probability = risk["probability"].lower()
354
 
355
  impact_key = f"{impact}_impact"
356
- probability_key = f"{probability}_probability"
 
 
 
 
 
357
 
358
- if impact_key in matrix and probability_key in matrix[impact_key]:
359
- matrix[impact_key][probability_key].append(risk)
 
 
 
 
 
 
 
 
360
 
361
  self.analysis_results["risk_matrix"] = matrix
362
 
363
  def _develop_mitigation_strategies(self, method):
364
  """تطوير استراتيجيات التخفيف"""
365
- mitigation_strategies = []
366
 
367
- for risk in self.analysis_results["identified_risks"]:
368
- strategy = self._generate_mitigation_strategy(risk, method)
369
- mitigation_strategies.append(strategy)
370
 
371
- self.analysis_results["mitigation_strategies"] = mitigation_strategies
372
-
373
- def _generate_mitigation_strategy(self, risk, method):
374
- """توليد استراتيجية تخفيف للمخاطرة"""
375
- strategy = {
376
- "risk_id": risk["id"],
377
- "risk_name": risk["name"],
378
- "risk_score": risk["risk_score"],
379
- "strategy_type": "",
380
- "actions": [],
381
- "responsible": "",
382
- "timeline": "",
383
- "cost_impact": 0
384
- }
385
 
386
- # تحديد نوع الاستراتيجية بناءً على درجة المخاطرة
387
- if risk["risk_score"] >= 6: # مخاطر عالية
388
- strategy["strategy_type"] = "تجنب"
389
- strategy["responsible"] = "مدير المشروع"
390
- strategy["timeline"] = "فوري"
391
- strategy["cost_impact"] = "عالي"
392
- elif risk["risk_score"] >= 3: # مخاطر متوسطة
393
- strategy["strategy_type"] = "تخفيف"
394
- strategy["responsible"] = "مشرف القسم المعني"
395
- strategy["timeline"] = "خلال أسبوعين"
396
- strategy["cost_impact"] = "متوسط"
397
- else: # مخاطر منخفضة
398
- strategy["strategy_type"] = "قبول"
399
- strategy["responsible"] = "فريق المشروع"
400
- strategy["timeline"] = "مراقبة مستمرة"
401
- strategy["cost_impact"] = "منخفض"
402
-
403
- # توليد إجراءات التخفيف بناءً على نوع المخاطرة وفئتها
404
- if risk["category"] == "توريد":
405
- strategy["actions"] = [
406
- "تحديد موردين بدلاء",
407
- "وضع جدول زمني للتوريد مع هوامش زمنية",
408
- "التعاقد المسبق على المواد الرئيسية"
409
- ]
410
- elif risk["category"] == "مالي":
411
- strategy["actions"] = [
 
 
 
 
 
 
 
 
 
 
 
 
 
412
  "تضمين بند تعديل الأسعار في العقود",
413
- "وضع ميزانية احتياطية",
414
- "التحوط ضد تقلبات الأسعار"
415
- ]
416
- elif risk["category"] == "بيئي":
417
- strategy["actions"] = [
418
- "وضع خطة للطوارئ الجوية",
419
- "جدولة الأنشطة الحساسة في الأوقات المناسبة",
420
- "توفير معدات وقاية مناسبة"
421
- ]
422
- elif risk["category"] == "موارد بشرية":
423
- strategy["actions"] = [
424
- "التعاقد المسبق مع مقاولي الباطن",
425
- "وضع خطة لتدريب وتأهيل العمالة",
426
- "تحفيز العاملين للحفاظ عليهم"
427
- ]
428
- elif risk["category"] == "فني":
429
- strategy["actions"] = [
430
- "إجراء دراسات فنية إضافية",
431
  "الاستعانة بخبراء متخصصين",
432
- "وضع خطط بديلة للحلول الفنية"
433
- ]
434
- elif risk["category"] == "إداري":
435
- strategy["actions"] = [
436
- "تحديد نطاق العم�� بدقة في العقود",
437
- "وضع إجراءات واضحة لإدارة التغيير",
438
- "التواصل المستمر مع العميل"
439
- ]
440
- elif risk["category"] == "تنظيمي":
441
- strategy["actions"] = [
442
- "متابعة التغييرات في اللوائح والأنظمة",
443
- "التنسيق المبكر مع الجهات المعنية",
444
- "تعيين مستشار قانوني للمشروع"
445
- ]
446
- else:
447
- strategy["actions"] = [
448
- "مراقبة المخاطر بشكل دوري",
449
- "وضع خطة استجابة",
450
- "تخصيص موارد احتياطية"
 
451
  ]
 
 
 
 
 
452
 
453
- # إضافة إجراءات إضافية إذا كانت طريقة التحليل شاملة
454
- if method == "comprehensive" and len(strategy["actions"]) < 5:
455
- strategy["actions"].append("إجراء مراجعات دورية لفعالية استراتيجية التخفيف")
456
- strategy["actions"].append("توثيق الدروس المستفادة لتحسين إدارة المخاطر المستقبلية")
457
 
458
- return strategy
 
 
 
 
 
 
 
459
 
460
  def _create_analysis_summary(self, method):
461
  """إنشاء ملخص التحليل"""
462
- identified_risks = self.analysis_results["identified_risks"]
 
 
 
463
 
464
- # حساب إحصائيات المخاطر
465
- total_risks = len(identified_risks)
466
- high_risks = sum(1 for risk in identified_risks if risk["risk_score"] >= 6)
467
- medium_risks = sum(1 for risk in identified_risks if 3 <= risk["risk_score"] < 6)
468
- low_risks = sum(1 for risk in identified_risks if risk["risk_score"] < 3)
 
 
469
 
470
  # حساب متوسط درجة المخاطرة
471
- avg_risk_score = sum(risk["risk_score"] for risk in identified_risks) / total_risks if total_risks > 0 else 0
472
-
473
- # تحديد أعلى فئات المخاطر
474
- categories = self.analysis_results["risk_categories"]
475
- top_categories = list(categories.keys())[:3] if len(categories) >= 3 else list(categories.keys())
476
-
477
- # تحديد المخاطر الحرجة
478
- critical_risks = [risk for risk in identified_risks if risk["risk_score"] >= 6]
479
- critical_risks = sorted(critical_risks, key=lambda x: x["risk_score"], reverse=True)
480
- top_critical_risks = critical_risks[:3] if len(critical_risks) >= 3 else critical_risks
481
 
482
- # إنشاء ملخص التحليل
483
  summary = {
484
  "total_risks": total_risks,
485
- "risk_distribution": {
486
- "high_risks": high_risks,
487
- "medium_risks": medium_risks,
488
- "low_risks": low_risks
489
- },
490
  "avg_risk_score": avg_risk_score,
491
- "top_risk_categories": top_categories,
492
- "critical_risks": [risk["name"] for risk in top_critical_risks],
493
- "overall_risk_level": self._determine_overall_risk_level(avg_risk_score, high_risks, total_risks),
494
- "recommendations": self._generate_recommendations(method, high_risks, avg_risk_score)
495
  }
496
 
497
  self.analysis_results["summary"] = summary
498
 
499
- def _determine_overall_risk_level(self, avg_risk_score, high_risks, total_risks):
500
- """تحديد مستوى المخاطرة الإجمالي"""
501
- if avg_risk_score >= 5 or (high_risks / total_risks >= 0.3 if total_risks > 0 else False):
502
- return "عالي"
503
- elif avg_risk_score >= 3 or (high_risks / total_risks >= 0.1 if total_risks > 0 else False):
504
- return "متوسط"
505
- else:
506
- return "منخفض"
507
-
508
- def _generate_recommendations(self, method, high_risks, avg_risk_score):
509
  """توليد توصيات بناءً على نتائج التحليل"""
510
  recommendations = []
511
 
512
- if high_risks > 0:
513
- recommendations.append("التركيز على استراتيجيات تخفيف المخاطر عالية الدرجة")
 
514
 
515
- if avg_risk_score >= 4:
516
- recommendations.append("إجراء مراجعة شاملة لخطة إدارة المخاطر بشكل دوري")
517
- recommendations.append("تخصيص ميزانية احتياطية كافية للتعامل مع المخاطر المحتملة")
518
 
519
- recommendations.append("توثيق المخاطر واستراتيجيات التخفيف في سجل المخاطر")
520
- recommendations.append("تعيين مسؤولين محددين لمتابعة تنفيذ استراتيجيات التخفيف")
 
 
521
 
522
- if method == "comprehensive":
523
- recommendations.append("إجراء تحليل كمي للمخاطر لتقدير التأثير المالي والزمني")
524
- recommendations.append("تطوير مؤشرات إنذار مبكر لرصد المخاطر قبل حدوثها")
525
- recommendations.append("إشراك أصحاب المصلحة في عملية تحديد وتقييم المخاطر")
526
 
527
  return recommendations
528
 
529
- def get_analysis_status(self):
530
- """الحصول على حالة التحليل الحالي"""
531
- if not self.analysis_in_progress:
532
- if not self.analysis_results:
533
- return {"status": "لا يوجد تحليل جارٍ"}
534
- else:
535
- return {"status": self.analysis_results.get("status", "غير معروف")}
536
-
537
- return {
538
- "status": "جاري التحليل",
539
- "project_id": self.current_project,
540
- "start_time": self.analysis_results.get("analysis_start_time")
541
- }
542
-
543
  def get_analysis_results(self):
544
  """الحصول على نتائج التحليل"""
545
  return self.analysis_results
546
 
547
- def export_analysis_results(self, output_path=None):
548
- """تصدير نتائج التحليل إلى ملف JSON"""
549
  if not self.analysis_results:
550
  logger.warning("لا توجد نتائج تحليل للتصدير")
551
  return None
552
 
553
- if not output_path:
554
- # إنشاء اسم ملف افتراضي
555
- timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
556
- filename = f"risk_analysis_results_{timestamp}.json"
557
- output_path = os.path.join(self.exports_path, filename)
558
 
559
- try:
560
- with open(output_path, 'w', encoding='utf-8') as f:
 
 
 
561
  json.dump(self.analysis_results, f, ensure_ascii=False, indent=4)
562
 
563
- logger.info(f"تم تصدير نتائج تحليل المخاطر إلى: {output_path}")
564
- return output_path
565
 
566
- except Exception as e:
567
- logger.error(f"خطأ في تصدير نتائج تحليل المخاطر: {str(e)}")
568
  return None
 
 
 
 
569
 
570
- def import_analysis_results(self, input_path):
571
- """استيراد نتائج التحليل من ملف JSON"""
572
- if not os.path.exists(input_path):
573
- logger.error(f"ملف نتائج التحليل غير موجود: {input_path}")
574
- return False
575
 
576
- try:
577
- with open(input_path, 'r', encoding='utf-8') as f:
578
- self.analysis_results = json.load(f)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
579
 
580
- logger.info(f"تم استيراد نتائج تحليل المخاطر من: {input_path}")
581
- return True
 
 
582
 
583
- except Exception as e:
584
- logger.error(f"خطأ في استيراد نتائج تحليل المخاطر: {str(e)}")
585
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  import datetime
9
  import json
10
  import math
11
+ import streamlit as st
12
  from pathlib import Path
13
+ import pandas as pd
14
+ import numpy as np
15
+ import matplotlib.pyplot as plt
16
+ import sys
17
+
18
+ # إضافة مسار المشروع للنظام
19
+ sys.path.append(str(Path(__file__).parent.parent))
20
+
21
+ # استيراد محسن واجهة المستخدم
22
+ from styling.enhanced_ui import UIEnhancer
23
 
24
  # تهيئة السجل
25
  logging.basicConfig(
 
255
  additional_risks.extend([
256
  {
257
  "id": 401,
258
+ "name": "مخاطر سياسية",
259
+ "description": "تغييرات في السياسات الحكومية أو اللوائح التنظيمية",
260
+ "category": "خارجي",
261
  "probability": "منخفض",
262
  "impact": "عالي",
263
  "risk_score": self._calculate_risk_score("منخفض", "عالي"),
 
265
  },
266
  {
267
  "id": 402,
268
+ "name": "مخاطر اقتصادية",
269
+ "description": "تقلبات في أسعار العملات أو التضخم",
270
+ "category": "مالي",
271
+ "probability": "متوسط",
272
  "impact": "متوسط",
273
+ "risk_score": self._calculate_risk_score("متوسط", "متوسط"),
274
  "source": "generated"
275
  },
276
  {
277
  "id": 403,
278
+ "name": "مخاطر تقنية",
279
+ "description": "مشاكل في التقنيات الجديدة أو المعدات",
280
+ "category": "فني",
281
  "probability": "متوسط",
282
  "impact": "متوسط",
283
  "risk_score": self._calculate_risk_score("متوسط", "متوسط"),
 
289
 
290
  def _calculate_risk_score(self, probability, impact):
291
  """حساب درجة المخاطرة"""
292
+ # تحويل القيم النصية إلى قيم رقمية
293
+ probability_values = {"منخفض": 1, "متوسط": 2, "عالي": 3}
294
+ impact_values = {"منخفض": 1, "متوسط": 2, "عالي": 3}
 
 
295
 
296
+ # حساب درجة المخاطرة
297
+ p_value = probability_values.get(probability, 1)
298
+ i_value = impact_values.get(impact, 1)
 
 
299
 
300
+ return p_value * i_value
 
 
 
301
 
302
  def _categorize_risks(self):
303
  """تصنيف المخاطر"""
 
305
 
306
  for risk in self.analysis_results["identified_risks"]:
307
  category = risk["category"]
 
308
  if category not in categories:
309
+ categories[category] = []
 
 
 
 
 
310
 
311
+ categories[category].append(risk)
 
 
312
 
313
+ # حساب إحصائيات لكل فئة
314
+ for category, risks in categories.items():
315
+ total_score = sum(risk["risk_score"] for risk in risks)
316
+ avg_score = total_score / len(risks) if risks else 0
317
+ max_score = max(risk["risk_score"] for risk in risks) if risks else 0
318
+
319
+ categories[category] = {
320
+ "risks": risks,
321
+ "count": len(risks),
322
+ "total_score": total_score,
323
+ "avg_score": avg_score,
324
+ "max_score": max_score
325
+ }
326
 
327
+ self.analysis_results["risk_categories"] = categories
328
 
329
  def _create_risk_matrix(self):
330
  """إنشاء مصفوفة المخاطر"""
331
  matrix = {
332
+ "high_impact": {"high_prob": [], "medium_prob": [], "low_prob": []},
333
+ "medium_impact": {"high_prob": [], "medium_prob": [], "low_prob": []},
334
+ "low_impact": {"high_prob": [], "medium_prob": [], "low_prob": []}
 
 
 
 
 
 
 
 
 
 
 
 
335
  }
336
 
337
  for risk in self.analysis_results["identified_risks"]:
 
339
  probability = risk["probability"].lower()
340
 
341
  impact_key = f"{impact}_impact"
342
+ if impact == "عالي":
343
+ impact_key = "high_impact"
344
+ elif impact == "متوسط":
345
+ impact_key = "medium_impact"
346
+ else:
347
+ impact_key = "low_impact"
348
 
349
+ prob_key = f"{probability}_prob"
350
+ if probability == "عالي":
351
+ prob_key = "high_prob"
352
+ elif probability == "متوسط":
353
+ prob_key = "medium_prob"
354
+ else:
355
+ prob_key = "low_prob"
356
+
357
+ if impact_key in matrix and prob_key in matrix[impact_key]:
358
+ matrix[impact_key][prob_key].append(risk)
359
 
360
  self.analysis_results["risk_matrix"] = matrix
361
 
362
  def _develop_mitigation_strategies(self, method):
363
  """تطوير استراتيجيات التخفيف"""
364
+ strategies = []
365
 
366
+ # استراتيجيات للمخاطر ذات الأولوية العالية
367
+ high_priority_risks = []
 
368
 
369
+ # المخاطر ذات التأثير العالي واحتمالية عالية
370
+ high_priority_risks.extend(self.analysis_results["risk_matrix"]["high_impact"]["high_prob"])
 
 
 
 
 
 
 
 
 
 
 
 
371
 
372
+ # المخاطر ذات التأثير العالي واحتمالية متوسطة
373
+ high_priority_risks.extend(self.analysis_results["risk_matrix"]["high_impact"]["medium_prob"])
374
+
375
+ # المخاطر ذات التأثير المتوسط واحتمالية عالية
376
+ high_priority_risks.extend(self.analysis_results["risk_matrix"]["medium_impact"]["high_prob"])
377
+
378
+ for risk in high_priority_risks:
379
+ strategy = self._generate_mitigation_strategy(risk)
380
+ strategies.append(strategy)
381
+
382
+ # إذا كانت طريقة التحليل شاملة، أضف استراتيجيات للمخاطر ذات الأولوية المتوسطة
383
+ if method == "comprehensive":
384
+ medium_priority_risks = []
385
+
386
+ # المخاطر ذات التأثير العالي واحتمالية منخفضة
387
+ medium_priority_risks.extend(self.analysis_results["risk_matrix"]["high_impact"]["low_prob"])
388
+
389
+ # المخاطر ذات التأثير المتوسط واحتمالية متوسطة
390
+ medium_priority_risks.extend(self.analysis_results["risk_matrix"]["medium_impact"]["medium_prob"])
391
+
392
+ # المخاطر ذات التأثير المنخفض واحتمالية عالية
393
+ medium_priority_risks.extend(self.analysis_results["risk_matrix"]["low_impact"]["high_prob"])
394
+
395
+ for risk in medium_priority_risks:
396
+ strategy = self._generate_mitigation_strategy(risk)
397
+ strategies.append(strategy)
398
+
399
+ self.analysis_results["mitigation_strategies"] = strategies
400
+
401
+ def _generate_mitigation_strategy(self, risk):
402
+ """توليد استراتيجية تخفيف للمخاطر"""
403
+ strategy_templates = {
404
+ "توريد": [
405
+ "إنشاء قائمة بموردين بديلين",
406
+ "التعاقد المسبق مع الموردين",
407
+ "تخزين المواد الحرجة مسبقًا",
408
+ "وضع خطة للتوريد المرحلي"
409
+ ],
410
+ "مالي": [
411
  "تضمين بند تعديل الأسعار في العقود",
412
+ "تخصيص ميزانية احتياطية",
413
+ "التأمين ضد المخاطر المالية",
414
+ "تحديث دراسة الجدوى بانتظام"
415
+ ],
416
+ "بيئي": [
417
+ "وضع خطة للطوارئ البيئية",
418
+ "جدولة الأنشطة الحرجة في المواسم المناسبة",
419
+ "توفير معدات حماية إضافية",
420
+ "تطبيق تقنيات مقاومة للظروف البيئية"
421
+ ],
422
+ "موارد بشرية": [
423
+ "التعاقد مع شركات توظيف إضافية",
424
+ "تدريب فرق العمل على مهام متعددة",
425
+ "وضع خطة للحوافز والمكافآت",
426
+ "تطوير برنامج للاحتفاظ بالموظفين"
427
+ ],
428
+ "فني": [
429
+ "إجراء اختبارات إضافية قبل التنفيذ",
430
  "الاستعانة بخبراء متخصصين",
431
+ "تطبيق منهجية مراجعة التصميم",
432
+ "إعداد خطط بديلة للحلول التقنية"
433
+ ],
434
+ "إداري": [
435
+ "تطبيق إجراءات إدارة التغيير",
436
+ "عقد اجتماعات دورية مع أصحاب المصلحة",
437
+ "توثيق متطلبات المشروع بشكل تفصيلي",
438
+ "تحديد نطاق العمل بوضوح في العقود"
439
+ ],
440
+ "تنظيمي": [
441
+ "التواصل المبكر مع الجهات التنظيمية",
442
+ "تعيين مستشار قانوني متخصص",
443
+ "متابعة التحديثات التنظيمية بانتظام",
444
+ "تخصيص وقت إضافي للحصول على الموافقات"
445
+ ],
446
+ "خارجي": [
447
+ "متابعة التطورات السياسية والاقتصادية",
448
+ "وضع خطط بديلة للسيناريوهات المختلفة",
449
+ "التأمين ضد المخاطر الخارجية",
450
+ "تنويع مصادر التوريد والتمويل"
451
  ]
452
+ }
453
+
454
+ # اختيار استراتيجيات مناسبة بناءً على فئة المخاطر
455
+ category = risk["category"]
456
+ templates = strategy_templates.get(category, strategy_templates["إداري"])
457
 
458
+ # اختيار استراتيجية عشوائية من القائمة
459
+ import random
460
+ strategy_text = random.choice(templates)
 
461
 
462
+ return {
463
+ "risk_id": risk["id"],
464
+ "risk_name": risk["name"],
465
+ "strategy": strategy_text,
466
+ "priority": "عالية" if risk["risk_score"] >= 6 else "متوسطة" if risk["risk_score"] >= 3 else "منخفضة",
467
+ "responsible": "مدير المشروع",
468
+ "timeline": "قبل بدء المشروع"
469
+ }
470
 
471
  def _create_analysis_summary(self, method):
472
  """إنشاء ملخص التحليل"""
473
+ total_risks = len(self.analysis_results["identified_risks"])
474
+ high_risks = len([r for r in self.analysis_results["identified_risks"] if r["risk_score"] >= 6])
475
+ medium_risks = len([r for r in self.analysis_results["identified_risks"] if 3 <= r["risk_score"] < 6])
476
+ low_risks = len([r for r in self.analysis_results["identified_risks"] if r["risk_score"] < 3])
477
 
478
+ # حساب توزيع المخاطر حسب الفئة
479
+ category_distribution = {}
480
+ for risk in self.analysis_results["identified_risks"]:
481
+ category = risk["category"]
482
+ if category not in category_distribution:
483
+ category_distribution[category] = 0
484
+ category_distribution[category] += 1
485
 
486
  # حساب متوسط درجة المخاطرة
487
+ avg_risk_score = sum(risk["risk_score"] for risk in self.analysis_results["identified_risks"]) / total_risks if total_risks > 0 else 0
 
 
 
 
 
 
 
 
 
488
 
489
+ # إنشاء الملخص
490
  summary = {
491
  "total_risks": total_risks,
492
+ "high_risks": high_risks,
493
+ "medium_risks": medium_risks,
494
+ "low_risks": low_risks,
495
+ "category_distribution": category_distribution,
 
496
  "avg_risk_score": avg_risk_score,
497
+ "analysis_method": method,
498
+ "recommendations": self._generate_recommendations(high_risks, medium_risks, low_risks, category_distribution)
 
 
499
  }
500
 
501
  self.analysis_results["summary"] = summary
502
 
503
+ def _generate_recommendations(self, high_risks, medium_risks, low_risks, category_distribution):
 
 
 
 
 
 
 
 
 
504
  """توليد توصيات بناءً على نتائج التحليل"""
505
  recommendations = []
506
 
507
+ # توصيات بناءً على عدد المخاطر العالية
508
+ if high_risks > 3:
509
+ recommendations.append("يجب إجراء مراجعة شاملة لخطة المشروع نظرًا لوجود عدد كبير من المخاطر عالية الخطورة.")
510
 
511
+ if high_risks > 0:
512
+ recommendations.append("تطوير خطط استجابة تفصيلية لجميع المخاطر عالية الخطورة.")
 
513
 
514
+ # توصيات بناءً على توزيع المخاطر حسب الفئة
515
+ max_category = max(category_distribution.items(), key=lambda x: x[1], default=(None, 0))
516
+ if max_category[0]:
517
+ recommendations.append(f"التركيز على إدارة مخاطر فئة '{max_category[0]}' حيث تمثل النسبة الأكبر من المخاطر المحددة.")
518
 
519
+ # توصيات عامة
520
+ recommendations.append("إجراء مراجعات دورية لسجل المخاطر وتحديثه بانتظام.")
521
+ recommendations.append("تعيين مسؤولين محددين لمتابعة استراتيجيات التخفيف من المخاطر.")
 
522
 
523
  return recommendations
524
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
  def get_analysis_results(self):
526
  """الحصول على نتائج التحليل"""
527
  return self.analysis_results
528
 
529
+ def export_analysis_results(self, format="json"):
530
+ """تصدير نتائج التحليل"""
531
  if not self.analysis_results:
532
  logger.warning("لا توجد نتائج تحليل للتصدير")
533
  return None
534
 
535
+ timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
536
+ project_id = self.analysis_results.get("project_id", "unknown")
 
 
 
537
 
538
+ if format == "json":
539
+ filename = f"risk_analysis_{project_id}_{timestamp}.json"
540
+ filepath = self.exports_path / filename
541
+
542
+ with open(filepath, 'w', encoding='utf-8') as f:
543
  json.dump(self.analysis_results, f, ensure_ascii=False, indent=4)
544
 
545
+ logger.info(f"تم تصدير نتائج التحليل إلى: {filepath}")
546
+ return filepath
547
 
548
+ else:
549
+ logger.error(f"تنسيق التصدير غير مدعوم: {format}")
550
  return None
551
+
552
+
553
+ class RiskAnalysisApp:
554
+ """تطبيق تحليل المخاطر"""
555
 
556
+ def __init__(self):
557
+ """تهيئة تطبيق تحليل المخاطر"""
558
+ self.ui = UIEnhancer(page_title="تحليل المخاطر - نظام تحليل المناقصات", page_icon="⚠️")
559
+ self.ui.apply_theme_colors()
560
+ self.risk_analyzer = RiskAnalyzer()
561
 
562
+ # تهيئة بيانات المشاريع
563
+ if 'projects' not in st.session_state:
564
+ st.session_state.projects = self._generate_sample_projects()
565
+
566
+ # تهيئة نتائج التحليل
567
+ if 'risk_analysis_results' not in st.session_state:
568
+ st.session_state.risk_analysis_results = {}
569
+
570
+ def run(self):
571
+ """تشغيل تطبيق تحليل المخاطر"""
572
+ # إنشاء قائمة العناصر
573
+ menu_items = [
574
+ {"name": "لوحة المعلومات", "icon": "house"},
575
+ {"name": "المناقصات والعقود", "icon": "file-text"},
576
+ {"name": "تحليل المستندات", "icon": "file-earmark-text"},
577
+ {"name": "نظام التسعير", "icon": "calculator"},
578
+ {"name": "حاسبة تكاليف البناء", "icon": "building"},
579
+ {"name": "الموارد والتكاليف", "icon": "people"},
580
+ {"name": "تحليل المخاطر", "icon": "exclamation-triangle"},
581
+ {"name": "إدارة المشاريع", "icon": "kanban"},
582
+ {"name": "الخرائط والمواقع", "icon": "geo-alt"},
583
+ {"name": "الجدول الزمني", "icon": "calendar3"},
584
+ {"name": "الإشعارات", "icon": "bell"},
585
+ {"name": "مقارنة المستندات", "icon": "files"},
586
+ {"name": "الترجمة", "icon": "translate"},
587
+ {"name": "المساعد الذكي", "icon": "robot"},
588
+ {"name": "التقارير", "icon": "bar-chart"},
589
+ {"name": "الإعدادات", "icon": "gear"}
590
+ ]
591
+
592
+ # إنشاء الشريط الجانبي
593
+ selected = self.ui.create_sidebar(menu_items)
594
+
595
+ # إنشاء ترويسة الصفحة
596
+ self.ui.create_header("تحليل المخاطر", "تحديد وتقييم وإدارة مخاطر المشاريع")
597
+
598
+ # عرض واجهة تحليل المخاطر
599
+ tabs = st.tabs([
600
+ "لوحة المعلومات",
601
+ "تحليل المخاطر",
602
+ "سجل المخاطر",
603
+ "مصفوفة المخاطر",
604
+ "استراتيجيات التخفيف"
605
+ ])
606
+
607
+ with tabs[0]:
608
+ self._render_dashboard_tab()
609
+
610
+ with tabs[1]:
611
+ self._render_analysis_tab()
612
+
613
+ with tabs[2]:
614
+ self._render_risk_register_tab()
615
+
616
+ with tabs[3]:
617
+ self._render_risk_matrix_tab()
618
+
619
+ with tabs[4]:
620
+ self._render_mitigation_strategies_tab()
621
+
622
+ def _render_dashboard_tab(self):
623
+ """عرض تبويب لوحة المعلومات"""
624
+
625
+ st.markdown("### لوحة معلومات تحليل المخاطر")
626
+
627
+ # عرض إحصائيات المخاطر
628
+ col1, col2, col3, col4 = st.columns(4)
629
+
630
+ # الحصول على إحصائيات المخاطر
631
+ total_risks = 0
632
+ high_risks = 0
633
+ medium_risks = 0
634
+ low_risks = 0
635
+
636
+ for project_id, results in st.session_state.risk_analysis_results.items():
637
+ if "identified_risks" in results:
638
+ project_risks = results["identified_risks"]
639
+ total_risks += len(project_risks)
640
+ high_risks += len([r for r in project_risks if r["risk_score"] >= 6])
641
+ medium_risks += len([r for r in project_risks if 3 <= r["risk_score"] < 6])
642
+ low_risks += len([r for r in project_risks if r["risk_score"] < 3])
643
+
644
+ with col1:
645
+ self.ui.create_metric_card("إجمالي المخاطر", str(total_risks), None, self.ui.COLORS['primary'])
646
+
647
+ with col2:
648
+ self.ui.create_metric_card("مخاطر عالية", str(high_risks), None, self.ui.COLORS['danger'])
649
+
650
+ with col3:
651
+ self.ui.create_metric_card("مخاطر متوسطة", str(medium_risks), None, self.ui.COLORS['warning'])
652
+
653
+ with col4:
654
+ self.ui.create_metric_card("مخاطر منخفضة", str(low_risks), None, self.ui.COLORS['success'])
655
+
656
+ # عرض توزيع المخاطر حسب الفئة
657
+ st.markdown("#### توزيع المخاطر حسب الفئة")
658
+
659
+ # جمع بيانات توزيع المخاطر
660
+ category_distribution = {}
661
+
662
+ for project_id, results in st.session_state.risk_analysis_results.items():
663
+ if "identified_risks" in results:
664
+ for risk in results["identified_risks"]:
665
+ category = risk["category"]
666
+ if category not in category_distribution:
667
+ category_distribution[category] = 0
668
+ category_distribution[category] += 1
669
+
670
+ if category_distribution:
671
+ # تحويل البيانات إلى DataFrame
672
+ category_df = pd.DataFrame({
673
+ 'الفئة': list(category_distribution.keys()),
674
+ 'عدد المخاطر': list(category_distribution.values())
675
+ })
676
 
677
+ # عرض الرسم البياني
678
+ st.bar_chart(category_df.set_index('الفئة'))
679
+ else:
680
+ st.info("لا توجد بيانات كافية لعرض توزيع المخاطر.")
681
 
682
+ # عرض المشاريع ذات المخاطر العالية
683
+ st.markdown("#### المشاريع ذات المخاطر العالية")
684
+
685
+ high_risk_projects = []
686
+
687
+ for project_id, results in st.session_state.risk_analysis_results.items():
688
+ if "identified_risks" in results:
689
+ project_high_risks = len([r for r in results["identified_risks"] if r["risk_score"] >= 6])
690
+ if project_high_risks > 0:
691
+ # البحث عن بيانات المشروع
692
+ project = next((p for p in st.session_state.projects if p["id"] == int(project_id)), None)
693
+ if project:
694
+ high_risk_projects.append({
695
+ 'اسم المشروع': project["name"],
696
+ 'رقم المناقصة': project["number"],
697
+ 'الجهة المالكة': project["client"],
698
+ 'عدد المخاطر العالية': project_high_risks
699
+ })
700
+
701
+ if high_risk_projects:
702
+ high_risk_df = pd.DataFrame(high_risk_projects)
703
+ st.dataframe(high_risk_df, use_container_width=True, hide_index=True)
704
+ else:
705
+ st.info("لا توجد مشاريع ذات مخاطر عالية حاليًا.")
706
+
707
+ def _render_analysis_tab(self):
708
+ """عرض تبويب تحليل ال��خاطر"""
709
+
710
+ st.markdown("### تحليل مخاطر المشروع")
711
+
712
+ # اختيار المشروع
713
+ project_options = [f"{p['name']} ({p['number']})" for p in st.session_state.projects]
714
+ selected_project = st.selectbox("اختر المشروع", project_options)
715
+
716
+ if selected_project:
717
+ # استخراج معرف المشروع من الاختيار
718
+ project_index = project_options.index(selected_project)
719
+ project = st.session_state.projects[project_index]
720
+ project_id = project["id"]
721
+
722
+ # عرض معلومات المشروع
723
+ col1, col2 = st.columns(2)
724
+
725
+ with col1:
726
+ st.markdown(f"**اسم المشروع**: {project['name']}")
727
+ st.markdown(f"**رقم المناقصة**: {project['number']}")
728
+ st.markdown(f"**الجهة المالكة**: {project['client']}")
729
+
730
+ with col2:
731
+ st.markdown(f"**الموقع**: {project['location']}")
732
+ st.markdown(f"**نوع المشروع**: {project['project_type']}")
733
+ st.markdown(f"**مستوى التعقيد**: {project['complexity']}")
734
+
735
+ # اختيار طريقة التحليل
736
+ analysis_method = st.radio(
737
+ "طريقة التحليل",
738
+ ["أساسي", "شامل"],
739
+ format_func=lambda x: "تحليل أساسي" if x == "أساسي" else "تحليل شامل"
740
+ )
741
+
742
+ # زر بدء التحليل
743
+ if st.button("بدء تحليل المخاطر"):
744
+ with st.spinner("جاري تحليل مخاطر المشروع..."):
745
+ # محاكاة وقت المعالجة
746
+ import time
747
+ time.sleep(2)
748
+
749
+ # إجراء تحليل المخاطر
750
+ self.risk_analyzer.analyze_risks(project_id, method="comprehensive" if analysis_method == "شامل" else "basic")
751
+
752
+ # الحصول على نتائج التحليل
753
+ results = self.risk_analyzer.get_analysis_results()
754
+
755
+ # تخزين النتائج في حالة الجلسة
756
+ st.session_state.risk_analysis_results[str(project_id)] = results
757
+
758
+ st.success("تم الانتهاء من تحليل المخاطر بنجاح!")
759
+ st.experimental_rerun()
760
+
761
+ # عرض نتائج التحليل إذا كانت متوفرة
762
+ if str(project_id) in st.session_state.risk_analysis_results:
763
+ results = st.session_state.risk_analysis_results[str(project_id)]
764
+
765
+ st.markdown("#### ملخص نتائج التحليل")
766
+
767
+ if "summary" in results:
768
+ summary = results["summary"]
769
+
770
+ col1, col2, col3 = st.columns(3)
771
+
772
+ with col1:
773
+ self.ui.create_metric_card("إجمالي المخاطر", str(summary["total_risks"]), None, self.ui.COLORS['primary'])
774
+
775
+ with col2:
776
+ self.ui.create_metric_card("مخاطر عالية", str(summary["high_risks"]), None, self.ui.COLORS['danger'])
777
+
778
+ with col3:
779
+ self.ui.create_metric_card("مخاطر متوسطة", str(summary["medium_risks"]), None, self.ui.COLORS['warning'])
780
+
781
+ # عرض توزيع المخاطر حسب الفئة
782
+ st.markdown("#### توزيع المخاطر حسب الفئة")
783
+
784
+ if "category_distribution" in summary:
785
+ category_df = pd.DataFrame({
786
+ 'الفئة': list(summary["category_distribution"].keys()),
787
+ 'عدد المخاطر': list(summary["category_distribution"].values())
788
+ })
789
+
790
+ st.bar_chart(category_df.set_index('الفئة'))
791
+
792
+ # عرض التوصيات
793
+ st.markdown("#### التوصيات")
794
+
795
+ if "recommendations" in summary:
796
+ for i, recommendation in enumerate(summary["recommendations"]):
797
+ st.markdown(f"{i+1}. {recommendation}")
798
+
799
+ # زر تصدير النتائج
800
+ if st.button("تصدير نتائج التحليل"):
801
+ with st.spinner("جاري تصدير النتائج..."):
802
+ # محاكاة وقت المعالجة
803
+ time.sleep(1)
804
+
805
+ # تصدير النتائج
806
+ export_path = self.risk_analyzer.export_analysis_results()
807
+
808
+ if export_path:
809
+ st.success(f"تم تصدير نتائج التحليل بنجاح!")
810
+ else:
811
+ st.error("حدث خطأ أثناء تصدير النتائج.")
812
+
813
+ def _render_risk_register_tab(self):
814
+ """عرض تبويب سجل المخاطر"""
815
+
816
+ st.markdown("### سجل المخاطر")
817
+
818
+ # اختيار المشروع
819
+ project_options = [f"{p['name']} ({p['number']})" for p in st.session_state.projects]
820
+ project_options.insert(0, "جميع المشاريع")
821
+ selected_project_option = st.selectbox("اختر المشروع", project_options, key="risk_register_project")
822
+
823
+ # جمع المخاطر المحددة
824
+ all_risks = []
825
+
826
+ if selected_project_option == "جميع المشاريع":
827
+ # جمع المخاطر من جميع المشاريع
828
+ for project_id, results in st.session_state.risk_analysis_results.items():
829
+ if "identified_risks" in results:
830
+ project = next((p for p in st.session_state.projects if p["id"] == int(project_id)), None)
831
+ project_name = project["name"] if project else f"مشروع {project_id}"
832
+
833
+ for risk in results["identified_risks"]:
834
+ risk_copy = risk.copy()
835
+ risk_copy["project_name"] = project_name
836
+ all_risks.append(risk_copy)
837
+ else:
838
+ # استخراج معرف المشروع من الاختيار
839
+ project_index = project_options.index(selected_project_option) - 1 # -1 لأننا أضفنا "جميع المشاريع" في البداية
840
+ project = st.session_state.projects[project_index]
841
+ project_id = project["id"]
842
+
843
+ # جمع المخاطر من المشروع المحدد
844
+ if str(project_id) in st.session_state.risk_analysis_results:
845
+ results = st.session_state.risk_analysis_results[str(project_id)]
846
+ if "identified_risks" in results:
847
+ for risk in results["identified_risks"]:
848
+ risk_copy = risk.copy()
849
+ risk_copy["project_name"] = project["name"]
850
+ all_risks.append(risk_copy)
851
+
852
+ # فلترة المخاطر
853
+ col1, col2, col3 = st.columns(3)
854
+
855
+ with col1:
856
+ category_filter = st.multiselect(
857
+ "فئة المخاطر",
858
+ list(set(risk["category"] for risk in all_risks)) if all_risks else [],
859
+ key="risk_register_category"
860
+ )
861
+
862
+ with col2:
863
+ probability_filter = st.multiselect(
864
+ "الاحتمالية",
865
+ ["عالي", "متوسط", "منخفض"],
866
+ key="risk_register_probability"
867
+ )
868
+
869
+ with col3:
870
+ impact_filter = st.multiselect(
871
+ "التأثير",
872
+ ["عالي", "متوسط", "منخفض"],
873
+ key="risk_register_impact"
874
+ )
875
+
876
+ # تطبيق الفلترة
877
+ filtered_risks = all_risks
878
+
879
+ if category_filter:
880
+ filtered_risks = [risk for risk in filtered_risks if risk["category"] in category_filter]
881
+
882
+ if probability_filter:
883
+ filtered_risks = [risk for risk in filtered_risks if risk["probability"] in probability_filter]
884
+
885
+ if impact_filter:
886
+ filtered_risks = [risk for risk in filtered_risks if risk["impact"] in impact_filter]
887
+
888
+ # عرض سجل المخاطر
889
+ if filtered_risks:
890
+ # تحويل المخاطر إلى DataFrame
891
+ risk_data = []
892
+ for risk in filtered_risks:
893
+ risk_data.append({
894
+ 'المشروع': risk.get("project_name", ""),
895
+ 'اسم المخاطرة': risk["name"],
896
+ 'الوصف': risk.get("description", ""),
897
+ 'الفئة': risk["category"],
898
+ 'الاحتمالية': risk["probability"],
899
+ 'التأثير': risk["impact"],
900
+ 'درجة المخاطرة': risk["risk_score"]
901
+ })
902
+
903
+ risk_df = pd.DataFrame(risk_data)
904
+
905
+ # ترتيب المخاطر حسب درجة المخاطرة (تنازليًا)
906
+ risk_df = risk_df.sort_values(by='درجة المخاطرة', ascending=False)
907
+
908
+ # عرض الجدول
909
+ st.dataframe(risk_df, use_container_width=True, hide_index=True)
910
+
911
+ # زر تصدير سجل المخاطر
912
+ if st.button("تصدير سجل المخاطر"):
913
+ with st.spinner("جاري تصدير سجل المخاطر..."):
914
+ # محاكاة وقت المعالجة
915
+ time.sleep(1)
916
+ st.success("تم تصدير سجل المخاطر بنجاح!")
917
+ else:
918
+ st.info("لا توجد مخاطر تطابق معايير البحث أو لم يتم إجراء تحليل للمخاطر بعد.")
919
+
920
+ def _render_risk_matrix_tab(self):
921
+ """عرض تبويب مصفوفة المخاطر"""
922
+
923
+ st.markdown("### مصفوفة المخاطر")
924
+
925
+ # اختيار المشروع
926
+ project_options = [f"{p['name']} ({p['number']})" for p in st.session_state.projects]
927
+ selected_project = st.selectbox("اختر المشروع", project_options, key="risk_matrix_project")
928
+
929
+ if selected_project:
930
+ # استخراج معرف المشروع من الاختيار
931
+ project_index = project_options.index(selected_project)
932
+ project = st.session_state.projects[project_index]
933
+ project_id = project["id"]
934
+
935
+ # التحقق من وجود نتائج تحليل للمشروع
936
+ if str(project_id) in st.session_state.risk_analysis_results:
937
+ results = st.session_state.risk_analysis_results[str(project_id)]
938
+
939
+ if "risk_matrix" in results:
940
+ matrix = results["risk_matrix"]
941
+
942
+ # إنشاء مصفوفة المخاطر
943
+ st.markdown("#### مصفوفة احتمالية وتأثير المخاطر")
944
+
945
+ # إنشاء بيانات المصفوفة
946
+ matrix_data = [
947
+ [len(matrix["high_impact"]["high_prob"]), len(matrix["high_impact"]["medium_prob"]), len(matrix["high_impact"]["low_prob"])],
948
+ [len(matrix["medium_impact"]["high_prob"]), len(matrix["medium_impact"]["medium_prob"]), len(matrix["medium_impact"]["low_prob"])],
949
+ [len(matrix["low_impact"]["high_prob"]), len(matrix["low_impact"]["medium_prob"]), len(matrix["low_impact"]["low_prob"])]
950
+ ]
951
+
952
+ # تحويل البيانات إلى DataFrame
953
+ matrix_df = pd.DataFrame(
954
+ matrix_data,
955
+ columns=["احتمالية عالية", "احتمالية متوسطة", "احتمالية منخفضة"],
956
+ index=["تأثير عالي", "تأثير متوسط", "تأثير منخفض"]
957
+ )
958
+
959
+ # عرض المصفوفة كجدول
960
+ st.dataframe(matrix_df)
961
+
962
+ # عرض تفاصيل المخاطر في كل خلية
963
+ st.markdown("#### تفاصيل المخاطر في المصفوفة")
964
+
965
+ # إنشاء تبويبات للخلايا المختلفة
966
+ matrix_tabs = st.tabs([
967
+ "تأثير عالي / احتمالية عالية",
968
+ "تأثير عالي / احتمالية متوسطة",
969
+ "تأثير متوسط / احتمالية عالية",
970
+ "تأثير متوسط / احتمالية متوسطة",
971
+ "أخرى"
972
+ ])
973
+
974
+ # عرض المخاطر في كل تبويب
975
+ with matrix_tabs[0]:
976
+ self._display_cell_risks(matrix["high_impact"]["high_prob"], "تأثير عالي / احتمالية عالية")
977
+
978
+ with matrix_tabs[1]:
979
+ self._display_cell_risks(matrix["high_impact"]["medium_prob"], "تأثير عالي / احتمالية متوسطة")
980
+
981
+ with matrix_tabs[2]:
982
+ self._display_cell_risks(matrix["medium_impact"]["high_prob"], "تأثير متوسط / احتمالية عالية")
983
+
984
+ with matrix_tabs[3]:
985
+ self._display_cell_risks(matrix["medium_impact"]["medium_prob"], "تأثير متوسط / احتمالية متوسطة")
986
+
987
+ with matrix_tabs[4]:
988
+ # جمع المخاطر الأخرى
989
+ other_risks = []
990
+ other_risks.extend(matrix["high_impact"]["low_prob"])
991
+ other_risks.extend(matrix["medium_impact"]["low_prob"])
992
+ other_risks.extend(matrix["low_impact"]["high_prob"])
993
+ other_risks.extend(matrix["low_impact"]["medium_prob"])
994
+ other_risks.extend(matrix["low_impact"]["low_prob"])
995
+
996
+ self._display_cell_risks(other_risks, "مخاطر أخرى")
997
+ else:
998
+ st.warning("لم يتم العثور على مصفوفة المخاطر للمشروع المحدد.")
999
+ else:
1000
+ st.warning("لم يتم إجراء تحليل للمخاطر لهذا المشروع بعد.")
1001
+
1002
+ def _display_cell_risks(self, risks, cell_title):
1003
+ """عرض المخاطر في خلية من مصفوفة المخاطر"""
1004
+
1005
+ if risks:
1006
+ st.markdown(f"##### {cell_title} ({len(risks)} مخاطر)")
1007
+
1008
+ # تحويل المخاطر إلى DataFrame
1009
+ risk_data = []
1010
+ for risk in risks:
1011
+ risk_data.append({
1012
+ 'اسم المخاطرة': risk["name"],
1013
+ 'الوصف': risk.get("description", ""),
1014
+ 'الفئة': risk["category"],
1015
+ 'درجة المخاطرة': risk["risk_score"]
1016
+ })
1017
+
1018
+ risk_df = pd.DataFrame(risk_data)
1019
+
1020
+ # عرض الجدول
1021
+ st.dataframe(risk_df, use_container_width=True, hide_index=True)
1022
+ else:
1023
+ st.info(f"لا توجد مخاطر في خلية {cell_title}.")
1024
+
1025
+ def _render_mitigation_strategies_tab(self):
1026
+ """عرض تبويب استراتيجيات التخفيف"""
1027
+
1028
+ st.markdown("### استراتيجيات التخفيف من المخاطر")
1029
+
1030
+ # اختيار المشروع
1031
+ project_options = [f"{p['name']} ({p['number']})" for p in st.session_state.projects]
1032
+ selected_project = st.selectbox("اختر المشروع", project_options, key="mitigation_project")
1033
+
1034
+ if selected_project:
1035
+ # استخراج معرف المشروع من الاختيار
1036
+ project_index = project_options.index(selected_project)
1037
+ project = st.session_state.projects[project_index]
1038
+ project_id = project["id"]
1039
+
1040
+ # التحقق من وجود نتائج تحليل للمشروع
1041
+ if str(project_id) in st.session_state.risk_analysis_results:
1042
+ results = st.session_state.risk_analysis_results[str(project_id)]
1043
+
1044
+ if "mitigation_strategies" in results and results["mitigation_strategies"]:
1045
+ strategies = results["mitigation_strategies"]
1046
+
1047
+ # فلترة الاستراتيجيات
1048
+ priority_filter = st.multiselect(
1049
+ "الأولوية",
1050
+ ["عالية", "متوسطة", "منخفضة"],
1051
+ default=["عالية"],
1052
+ key="mitigation_priority"
1053
+ )
1054
+
1055
+ # تطبيق الفلترة
1056
+ filtered_strategies = strategies
1057
+ if priority_filter:
1058
+ filtered_strategies = [s for s in filtered_strategies if s["priority"] in priority_filter]
1059
+
1060
+ # عرض استراتيجيات التخفيف
1061
+ if filtered_strategies:
1062
+ # تحويل الاستراتيجيات إلى DataFrame
1063
+ strategy_data = []
1064
+ for strategy in filtered_strategies:
1065
+ strategy_data.append({
1066
+ 'المخاطرة': strategy["risk_name"],
1067
+ 'استراتيجية التخفيف': strategy["strategy"],
1068
+ 'الأولوية': strategy["priority"],
1069
+ 'المسؤول': strategy["responsible"],
1070
+ 'الإطار الزمني': strategy["timeline"]
1071
+ })
1072
+
1073
+ strategy_df = pd.DataFrame(strategy_data)
1074
+
1075
+ # ترتيب الاستراتيجيات حسب الأولوية
1076
+ priority_order = {"عالية": 0, "متوسطة": 1, "منخفضة": 2}
1077
+ strategy_df["priority_order"] = strategy_df["الأولوية"].map(priority_order)
1078
+ strategy_df = strategy_df.sort_values(by="priority_order")
1079
+ strategy_df = strategy_df.drop(columns=["priority_order"])
1080
+
1081
+ # عرض الجدول
1082
+ st.dataframe(strategy_df, use_container_width=True, hide_index=True)
1083
+
1084
+ # زر تصدير استراتيجيات التخفيف
1085
+ if st.button("تصدير استراتيجيات التخفيف"):
1086
+ with st.spinner("جاري تصدير استراتيجيات التخفيف..."):
1087
+ # محاكاة وقت المعالجة
1088
+ time.sleep(1)
1089
+ st.success("تم تصدير استراتيجيات التخفيف بنجاح!")
1090
+ else:
1091
+ st.info("لا توجد استراتيجيات تخفيف تطابق معايير الفلترة.")
1092
+ else:
1093
+ st.warning("لم يتم العثور على استراتيجيات تخفيف للمشروع المحدد.")
1094
+ else:
1095
+ st.warning("لم يتم إجراء تحليل للمخاطر لهذا المشروع بعد.")
1096
+
1097
+ def _generate_sample_projects(self):
1098
+ """توليد بيانات افتراضية للمشاريع"""
1099
+
1100
+ return [
1101
+ {
1102
+ 'id': 1,
1103
+ 'name': "إنشاء مبنى مستشفى الولادة والأطفال بمنطقة الشرقية",
1104
+ 'number': "SHPD-2025-001",
1105
+ 'client': "وزارة الصحة",
1106
+ 'location': "الدمام، المنطقة الشرقية",
1107
+ 'description': "يشمل المشروع إنشاء وتجهيز مبنى مستشفى الولادة والأطفال بسعة 300 سرير، ويتكون المبنى من 4 طوابق بمساحة إجمالية 15,000 متر مربع.",
1108
+ 'status': "قيد التسعير",
1109
+ 'tender_type': "عامة",
1110
+ 'pricing_method': "قياسي",
1111
+ 'submission_date': (datetime.datetime.now() + datetime.timedelta(days=5)),
1112
+ 'created_at': datetime.datetime.now() - datetime.timedelta(days=10),
1113
+ 'created_by_id': 1,
1114
+ 'project_type': "مباني",
1115
+ 'complexity': "عالي"
1116
+ },
1117
+ {
1118
+ 'id': 2,
1119
+ 'name': "صيانة وتطوير طريق الملك عبدالله",
1120
+ 'number': "MOT-2025-042",
1121
+ 'client': "وزارة النقل",
1122
+ 'location': "الرياض، المنطقة الوسطى",
1123
+ 'description': "صيانة وتطوير طريق الملك عبدالله بطول 25 كم، ويشمل المشروع إعادة الرصف وتحسين الإنارة وتركيب اللوحات الإرشادية.",
1124
+ 'status': "تم التقديم",
1125
+ 'tender_type': "عامة",
1126
+ 'pricing_method': "غير متزن",
1127
+ 'submission_date': (datetime.datetime.now() - datetime.timedelta(days=15)),
1128
+ 'created_at': datetime.datetime.now() - datetime.timedelta(days=45),
1129
+ 'created_by_id': 1,
1130
+ 'project_type': "بنية تحتية",
1131
+ 'complexity': "متوسط"
1132
+ },
1133
+ {
1134
+ 'id': 3,
1135
+ 'name': "إنشاء محطة معالجة مياه الصرف الصحي",
1136
+ 'number': "SWPC-2025-007",
1137
+ 'client': "شركة المياه الوطنية",
1138
+ 'location': "جدة، المنطقة الغربية",
1139
+ 'description': "إنشاء محطة معالجة مياه الصرف الصحي بطاقة استيعابية 50,000 م3/يوم، مع جميع الأعمال المدنية والكهروميكانيكية.",
1140
+ 'status': "تمت الترسية",
1141
+ 'tender_type': "عامة",
1142
+ 'pricing_method': "قياسي",
1143
+ 'submission_date': (datetime.datetime.now() - datetime.timedelta(days=90)),
1144
+ 'created_at': datetime.datetime.now() - datetime.timedelta(days=120),
1145
+ 'created_by_id': 1,
1146
+ 'project_type': "بنية تحتية",
1147
+ 'complexity': "عالي"
1148
+ }
1149
+ ]
1150
+
1151
+ # تشغيل التطبيق
1152
+ if __name__ == "__main__":
1153
+ risk_app = RiskAnalysisApp()
1154
+ risk_app.run()
styling/enhanced_ui.py CHANGED
@@ -1,513 +1,612 @@
1
  """
2
- تحسينات التصميم المرئي وواجهة المستخدم لنظام تحليل المناقصات
3
  """
4
 
5
  import streamlit as st
6
- import streamlit_option_menu as option_menu
7
- from streamlit_extras.colored_header import colored_header
8
- from streamlit_extras.switch_page_button import switch_page
9
- import os
10
- import sys
11
  from pathlib import Path
12
-
13
- # إضافة مسار المشروع للنظام
14
- sys.path.append(str(Path(__file__).parent.parent))
15
 
16
  class UIEnhancer:
17
- """فئة لتحسين واجهة المستخدم وتوحيد التصميم المرئي عبر النظام"""
18
 
19
  # ألوان النظام
20
  COLORS = {
21
- 'primary': '#1E5F74', # أزرق داكن للعناصر الرئيسية
22
- 'secondary': '#4BA3C3', # أزرق فاتح للعناصر الثانوية
23
- 'accent': '#F39237', # برتقالي للتأكيد والأزرار المهمة
24
- 'success': '#4CAF50', # أخضر للنجاح
25
- 'warning': '#FFC107', # أصفر للتحذيرات
26
- 'danger': '#E63946', # أحمر للأخطاء
27
- 'light': '#F5F5F5', # فاتح للخلفيات في الوضع الفاتح
28
- 'dark': '#1A1A1A', # داكن للخلفيات في الوضع الداكن
29
- 'text_light': '#FFFFFF', # نص أبيض للوضع الداكن
30
- 'text_dark': '#333333', # نص داكن للوضع الفاتح
31
- 'border': '#DDDDDD', # لون الحدود
32
- 'hover': '#2A7F9E', # لون التحويم
33
  }
34
 
35
- # أنماط CSS المخصصة
36
- CUSTOM_CSS = """
37
- <style>
38
- /* تخصيص الشعار والعنوان */
39
- .logo-title {
40
- display: flex;
41
- align-items: center;
42
- margin-bottom: 1rem;
43
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
- .logo-img {
46
- height: 60px;
47
- margin-right: 10px;
48
- }
49
 
50
- /* تخصيص البطاقات */
51
- .card {
52
- border-radius: 10px;
53
- padding: 1.5rem;
 
 
 
 
 
54
  margin-bottom: 1rem;
55
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
56
- transition: transform 0.3s ease, box-shadow 0.3s ease;
57
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
- .card:hover {
60
- transform: translateY(-5px);
61
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
62
- }
 
63
 
64
- /* تخصيص الأزرار */
65
- .custom-button {
66
- border-radius: 8px;
67
- padding: 0.5rem 1rem;
68
- font-weight: 500;
69
- transition: all 0.3s ease;
70
- }
71
 
72
- .custom-button:hover {
73
- opacity: 0.9;
74
- transform: translateY(-2px);
75
- }
76
 
77
- /* تخصيص القوائم */
78
- .nav-link {
79
- border-radius: 8px;
80
- margin: 0.2rem 0;
81
- transition: all 0.3s ease;
82
- }
83
 
84
- .nav-link:hover {
85
- transform: translateX(5px);
86
- }
87
 
88
- /* تخصيص الجداول */
89
- .styled-table {
90
- width: 100%;
91
- border-collapse: separate;
92
- border-spacing: 0;
 
 
 
 
 
 
93
  border-radius: 10px;
94
- overflow: hidden;
95
- }
 
 
 
96
 
97
- .styled-table th {
98
- background-color: var(--primary-color);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  color: white;
100
- padding: 12px 15px;
101
- text-align: right;
102
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
- .styled-table td {
105
- padding: 12px 15px;
106
- border-bottom: 1px solid var(--border-color);
107
- }
 
 
 
 
 
 
 
 
 
 
108
 
109
- .styled-table tr:last-child td {
110
- border-bottom: none;
111
- }
 
 
112
 
113
- .styled-table tr:nth-child(even) {
114
- background-color: rgba(0, 0, 0, 0.05);
115
- }
 
116
 
117
- /* تخصيص لوحات المعلومات */
118
- .dashboard-metric {
119
- text-align: center;
120
- padding: 1rem;
121
- border-radius: 10px;
122
- background-color: var(--secondary-color);
 
 
123
  color: white;
124
- }
 
 
125
 
126
- .dashboard-metric h3 {
127
- font-size: 2rem;
128
- margin: 0;
129
- }
130
 
131
- .dashboard-metric p {
132
- margin: 0;
133
- opacity: 0.8;
134
- }
135
 
136
- /* تخصيص الرسوم البيانية */
137
- .chart-container {
138
- border-radius: 10px;
139
- padding: 1rem;
 
 
 
140
  background-color: white;
 
 
141
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
142
- }
143
-
144
- /* تخصيص الإشعارات */
145
- .notification {
146
- padding: 1rem;
147
- border-radius: 8px;
148
- margin-bottom: 0.5rem;
149
- display: flex;
150
- align-items: center;
151
- }
152
 
153
- .notification-icon {
154
- margin-left: 1rem;
155
- font-size: 1.5rem;
156
- }
157
-
158
- /* تخصيص الشريط الجانبي */
159
- .sidebar .sidebar-content {
160
- background-color: var(--primary-color);
161
  color: white;
162
- }
163
 
164
- /* تحسين اتجاه النص للغة العربية */
165
- body {
166
- direction: rtl;
167
- text-align: right;
168
- }
169
 
170
- /* تخصيص الخط */
171
- @font-face {
172
- font-family: 'Tajawal';
173
- src: url('https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700&display=swap');
174
- }
 
 
 
175
 
176
- * {
177
- font-family: 'Tajawal', sans-serif;
178
- }
179
- </style>
180
- """
181
 
182
- def __init__(self, page_title="نظام تحليل المناقصات", page_icon="📊", layout="wide"):
183
- """تهيئة محسن واجهة المستخدم"""
184
- self.page_title = page_title
185
- self.page_icon = page_icon
186
- self.layout = layout
187
- self.setup_page()
188
-
189
- def setup_page(self):
190
- """إعداد الصفحة وتطبيق الإعدادات العامة"""
191
- st.set_page_config(
192
- page_title=self.page_title,
193
- page_icon=self.page_icon,
194
- layout=self.layout,
195
- initial_sidebar_state="expanded"
196
- )
197
-
198
- # تطبيق CSS المخصص
199
- st.markdown(self.CUSTOM_CSS, unsafe_allow_html=True)
 
200
 
201
- # إعداد متغيرات الجلسة إذا لم تكن موجودة
202
- if 'theme' not in st.session_state:
203
- st.session_state.theme = 'light'
204
- if 'language' not in st.session_state:
205
- st.session_state.language = 'ar'
 
 
 
 
 
206
 
207
  def create_sidebar(self, menu_items):
208
- """إنشاء شريط جانبي محسن مع قائمة"""
209
  with st.sidebar:
210
- # عرض الشعار والعنوان
211
  st.markdown(
212
  f"""
213
- <div class="logo-title">
214
- <img src="./assets/images/logo.png" class="logo-img">
215
- <h2>{self.page_title}</h2>
216
  </div>
217
  """,
218
  unsafe_allow_html=True
219
  )
220
 
221
- # إنشاء قائمة الخيارات
222
- selected = option_menu.option_menu(
223
- menu_title=None,
224
- options=[item["name"] for item in menu_items],
225
- icons=[item["icon"] for item in menu_items],
226
- menu_icon="cast",
227
- default_index=0,
228
- styles={
229
- "container": {"padding": "0!important", "background-color": f"{self.COLORS['primary']}"},
230
- "icon": {"color": "white", "font-size": "18px"},
231
- "nav-link": {"color": "white", "font-size": "16px", "text-align": "right", "margin":"0px"},
232
- "nav-link-selected": {"background-color": f"{self.COLORS['accent']}"},
233
- }
234
- )
235
-
236
- # إضافة مفتاح تبديل السمة
237
- st.markdown("<hr>", unsafe_allow_html=True)
238
- col1, col2 = st.columns([1, 3])
239
- with col1:
240
- st.write("السمة:")
241
- with col2:
242
- if st.toggle("الوضع الداكن", st.session_state.theme == 'dark'):
243
- st.session_state.theme = 'dark'
244
- else:
245
- st.session_state.theme = 'light'
246
-
247
  # إضافة معلومات المستخدم
248
- st.markdown("<hr>", unsafe_allow_html=True)
249
  st.markdown(
250
  f"""
251
- <div style="text-align: center;">
252
- <p>مرحباً، المستخدم</p>
253
- <button class="custom-button" style="background-color: {self.COLORS['danger']}; color: white; border: none;">تسجيل الخروج</button>
 
 
 
254
  </div>
255
  """,
256
  unsafe_allow_html=True
257
  )
258
 
259
- return selected
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
 
261
- def create_header(self, title, description=None):
262
- """إنشاء ترويسة محسنة للصفحة"""
263
- colored_header(
264
- label=title,
265
- description=description,
266
- color_name=self.COLORS['primary']
267
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
 
269
- def create_card(self, title, content, color=None):
270
- """إنشاء بطاقة محسنة"""
271
- bg_color = color if color else self.COLORS['light'] if st.session_state.theme == 'light' else self.COLORS['dark']
272
- text_color = self.COLORS['text_dark'] if st.session_state.theme == 'light' else self.COLORS['text_light']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
 
274
  st.markdown(
275
  f"""
276
- <div class="card" style="background-color: {bg_color}; color: {text_color};">
277
- <h3>{title}</h3>
278
- <p>{content}</p>
 
279
  </div>
280
  """,
281
  unsafe_allow_html=True
282
  )
283
 
284
- def create_metric_card(self, title, value, delta=None, color=None):
285
- """إنشاء بطاقة مقاييس محسنة"""
286
- bg_color = color if color else self.COLORS['primary']
 
287
 
288
- delta_html = f"<span style='color: {'green' if delta > 0 else 'red'};'>{delta}%</span>" if delta is not None else ""
 
 
289
 
290
  st.markdown(
291
  f"""
292
- <div class="dashboard-metric" style="background-color: {bg_color};">
293
- <p>{title}</p>
294
- <h3>{value}</h3>
295
- {delta_html}
296
  </div>
297
  """,
298
  unsafe_allow_html=True
299
  )
300
 
301
- def create_notification(self, message, type_="info"):
302
- """إنشاء إشعار محسن"""
303
- colors = {
304
- "info": self.COLORS['secondary'],
305
- "success": self.COLORS['success'],
306
- "warning": self.COLORS['warning'],
307
- "error": self.COLORS['danger']
 
 
 
308
  }
309
 
310
- icons = {
311
- "info": "ℹ️",
312
- "success": "✅",
313
- "warning": "⚠️",
314
- "error": "❌"
315
- }
316
 
317
- bg_color = colors.get(type_, colors["info"])
318
- icon = icons.get(type_, icons["info"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
 
320
  st.markdown(
321
  f"""
322
- <div class="notification" style="background-color: {bg_color};">
323
- <div class="notification-icon">{icon}</div>
324
- <div>{message}</div>
 
 
 
 
 
325
  </div>
326
  """,
327
  unsafe_allow_html=True
328
  )
329
 
330
- def create_button(self, label, type_="primary", on_click=None):
331
- """إنشاء زر محسن"""
332
- colors = {
333
- "primary": self.COLORS['primary'],
334
- "secondary": self.COLORS['secondary'],
335
- "accent": self.COLORS['accent'],
336
- "success": self.COLORS['success'],
337
- "warning": self.COLORS['warning'],
338
- "danger": self.COLORS['danger']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
 
341
- bg_color = colors.get(type_, colors["primary"])
342
- text_color = self.COLORS['text_light']
 
343
 
344
- return st.button(
345
- label,
346
- key=f"btn_{label}_{type_}",
347
- on_click=on_click,
348
- use_container_width=True
349
- )
350
-
351
- def create_table(self, data, columns):
352
- """إنشاء جدول محسن"""
353
- # تحويل البيانات إلى HTML
354
- table_html = f"""
355
- <table class="styled-table">
356
- <thead>
357
- <tr>
358
- {"".join([f"<th>{col}</th>" for col in columns])}
359
- </tr>
360
- </thead>
361
- <tbody>
362
  """
363
 
364
- for row in data:
365
- table_html += "<tr>"
366
- for col in columns:
367
- table_html += f"<td>{row.get(col, '')}</td>"
368
- table_html += "</tr>"
 
 
 
369
 
370
- table_html += """
371
- </tbody>
372
- </table>
373
  """
374
 
375
- st.markdown(table_html, unsafe_allow_html=True)
376
 
377
- def apply_theme_colors(self):
378
- """تطبيق ألوان السمة الحالية"""
379
- theme_colors = {
380
- 'light': {
381
- 'background': self.COLORS['light'],
382
- 'text': self.COLORS['text_dark'],
383
- 'border': self.COLORS['border']
384
- },
385
- 'dark': {
386
- 'background': self.COLORS['dark'],
387
- 'text': self.COLORS['text_light'],
388
- 'border': '#444444'
389
- }
390
- }
391
-
392
- current_theme = theme_colors[st.session_state.theme]
393
 
 
 
 
 
 
 
394
  st.markdown(
395
- f"""
396
- <style>
397
- :root {{
398
- --background-color: {current_theme['background']};
399
- --text-color: {current_theme['text']};
400
- --border-color: {current_theme['border']};
401
- --primary-color: {self.COLORS['primary']};
402
- --secondary-color: {self.COLORS['secondary']};
403
- --accent-color: {self.COLORS['accent']};
404
- }}
405
-
406
- .stApp {{
407
- background-color: var(--background-color);
408
- color: var(--text-color);
409
- }}
410
-
411
- .card {{
412
- background-color: var(--background-color);
413
- color: var(--text-color);
414
- border: 1px solid var(--border-color);
415
- }}
416
- </style>
417
  """,
418
  unsafe_allow_html=True
419
  )
420
-
421
- # استخدام الفئة
422
- if __name__ == "__main__":
423
- ui = UIEnhancer()
424
- ui.apply_theme_colors()
425
-
426
- # إنشاء قائمة العناصر
427
- menu_items = [
428
- {"name": "لوحة المعلومات", "icon": "house"},
429
- {"name": "المناقصات والعقود", "icon": "file-text"},
430
- {"name": "تحليل المستندات", "icon": "file-earmark-text"},
431
- {"name": "نظام التسعير", "icon": "calculator"},
432
- {"name": "حاسبة تكاليف البناء", "icon": "building"},
433
- {"name": "الموارد والتكاليف", "icon": "people"},
434
- {"name": "تحليل المخاطر", "icon": "exclamation-triangle"},
435
- {"name": "إدارة المشاريع", "icon": "kanban"},
436
- {"name": "الخرائط والمواقع", "icon": "geo-alt"},
437
- {"name": "الجدول الزمني", "icon": "calendar3"},
438
- {"name": "الإشعارات", "icon": "bell"},
439
- {"name": "مقارنة المستندات", "icon": "files"},
440
- {"name": "المساعد الذكي", "icon": "robot"},
441
- {"name": "التقارير", "icon": "bar-chart"},
442
- {"name": "الإعدادات", "icon": "gear"}
443
- ]
444
-
445
- # إنشاء الشريط الجانبي
446
- selected = ui.create_sidebar(menu_items)
447
-
448
- # إنشاء ترويسة الصفحة
449
- ui.create_header("لوحة المعلومات", "نظرة عامة على المناقصات والمشاريع")
450
-
451
- # إنشاء صفوف للمقاييس
452
- col1, col2, col3, col4 = st.columns(4)
453
-
454
- with col1:
455
- ui.create_metric_card("المناقصات النشطة", "12", delta=5)
456
-
457
- with col2:
458
- ui.create_metric_card("المشاريع الجارية", "8", delta=-2)
459
-
460
- with col3:
461
- ui.create_metric_card("المناقصات المربوحة", "65%", delta=10)
462
-
463
- with col4:
464
- ui.create_metric_card("قيمة المشاريع", "25M ريال", delta=15)
465
-
466
- # إنشاء صفين للبطاقات والإشعارات
467
- col1, col2 = st.columns([2, 1])
468
-
469
- with col1:
470
- ui.create_header("المناقصات الحالية", "آخر تحديث: اليوم")
471
-
472
- # بيانات المناقصات
473
- tenders_data = [
474
- {"رقم المناقصة": "T-2025-001", "العنوان": "إنشاء مبنى إداري", "الحالة": "قيد الدراسة", "تاريخ التقديم": "2025-04-15"},
475
- {"رقم المناقصة": "T-2025-002", "العنوان": "صيانة طرق", "الحالة": "تم التقديم", "تاريخ التقديم": "2025-03-20"},
476
- {"رقم المناقصة": "T-2025-003", "العنوان": "توريد معدات", "الحالة": "فائز", "تاريخ التقديم": "2025-02-10"}
477
- ]
478
-
479
- ui.create_table(tenders_data, ["رقم المناقصة", "العنوان", "الحالة", "تاريخ التقديم"])
480
-
481
- with col2:
482
- ui.create_header("الإشعارات", "آخر التنبيهات")
483
-
484
- ui.create_notification("موعد تسليم مناقصة T-2025-001 بعد 5 أيام", "warning")
485
- ui.create_notification("تم ترسية مناقصة T-2025-003", "success")
486
- ui.create_notification("تم تحديث مستندات مناقصة T-2025-002", "info")
487
-
488
- st.markdown("<br>", unsafe_allow_html=True)
489
- ui.create_button("عرض جميع الإشعارات", "secondary")
490
-
491
- # إنشاء صف للبطاقات
492
- st.markdown("<br>", unsafe_allow_html=True)
493
- ui.create_header("الإجراءات السريعة", "اختر إجراءً للبدء")
494
-
495
- col1, col2, col3 = st.columns(3)
496
-
497
- with col1:
498
- ui.create_card(
499
- "إضافة مناقصة جديدة",
500
- "إنشاء مناقصة جديدة وإدخال البيانات الأساسية"
501
- )
502
 
503
- with col2:
504
- ui.create_card(
505
- "تحليل مستند",
506
- "تحميل مستند مناقصة جديد للتحليل التلقائي"
507
- )
508
 
509
- with col3:
510
- ui.create_card(
511
- "إنشاء تقرير",
512
- "إنشاء تقارير مخصصة للمناقصات والمشاريع"
513
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
+ محسن واجهة المستخدم - نظام تحليل المناقصات
3
  """
4
 
5
  import streamlit as st
6
+ import pandas as pd
7
+ import numpy as np
8
+ import base64
 
 
9
  from pathlib import Path
10
+ import os
 
 
11
 
12
  class UIEnhancer:
13
+ """فئة لتحسين واجهة المستخدم وتوحيد التصميم عبر النظام"""
14
 
15
  # ألوان النظام
16
  COLORS = {
17
+ 'primary': '#1E88E5', # أزرق
18
+ 'secondary': '#5E35B1', # بنفسجي
19
+ 'success': '#43A047', # أخضر
20
+ 'warning': '#FB8C00', # برتقالي
21
+ 'danger': '#E53935', # أحمر
22
+ 'info': '#00ACC1', # سماوي
23
+ 'light': '#F5F5F5', # رمادي فاتح
24
+ 'dark': '#212121', # رمادي داكن
25
+ 'accent': '#FF4081', # وردي
26
+ 'background': '#FFFFFF', # أبيض
27
+ 'text': '#212121', # أسود
28
+ 'border': '#E0E0E0' # رمادي حدود
29
  }
30
 
31
+ # أحجام الخطوط
32
+ FONT_SIZES = {
33
+ 'xs': '0.75rem',
34
+ 'sm': '0.875rem',
35
+ 'md': '1rem',
36
+ 'lg': '1.125rem',
37
+ 'xl': '1.25rem',
38
+ '2xl': '1.5rem',
39
+ '3xl': '1.875rem',
40
+ '4xl': '2.25rem',
41
+ '5xl': '3rem'
42
+ }
43
+
44
+ def __init__(self, page_title="نظام تحليل المناقصات", page_icon="📊"):
45
+ """تهيئة محسن واجهة المستخدم"""
46
+ self.page_title = page_title
47
+ self.page_icon = page_icon
48
+ self.theme_mode = "light" # الوضع الافتراضي هو الوضع الفاتح
49
+
50
+ def apply_global_styles(self):
51
+ """تطبيق التنسيقات العامة على الصفحة"""
52
+ # تعريف CSS العام
53
+ css = f"""
54
+ @import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700&display=swap');
55
 
56
+ * {{
57
+ font-family: 'Tajawal', sans-serif;
58
+ direction: rtl;
59
+ }}
60
 
61
+ h1, h2, h3, h4, h5, h6 {{
62
+ font-family: 'Tajawal', sans-serif;
63
+ font-weight: 700;
64
+ color: {self.COLORS['dark']};
65
+ }}
66
+
67
+ .module-title {{
68
+ color: {self.COLORS['primary']};
69
+ font-size: {self.FONT_SIZES['3xl']};
70
  margin-bottom: 1rem;
71
+ border-bottom: 2px solid {self.COLORS['primary']};
72
+ padding-bottom: 0.5rem;
73
+ }}
74
+
75
+ .stTabs [data-baseweb="tab-list"] {{
76
+ gap: 2px;
77
+ }}
78
+
79
+ .stTabs [data-baseweb="tab"] {{
80
+ height: 50px;
81
+ white-space: pre-wrap;
82
+ background-color: {self.COLORS['light']};
83
+ border-radius: 4px 4px 0 0;
84
+ gap: 1px;
85
+ padding-top: 10px;
86
+ padding-bottom: 10px;
87
+ }}
88
+
89
+ .stTabs [aria-selected="true"] {{
90
+ background-color: {self.COLORS['primary']};
91
+ color: white;
92
+ }}
93
 
94
+ div[data-testid="stSidebarNav"] li div a span {{
95
+ direction: rtl;
96
+ text-align: right;
97
+ font-family: 'Tajawal', sans-serif;
98
+ }}
99
 
100
+ div[data-testid="stSidebarNav"] {{
101
+ background-color: {self.COLORS['light']};
102
+ }}
 
 
 
 
103
 
104
+ div[data-testid="stSidebarNav"] li div {{
105
+ margin-right: 0;
106
+ margin-left: auto;
107
+ }}
108
 
109
+ div[data-testid="stSidebarNav"] li div a {{
110
+ padding-right: 10px;
111
+ padding-left: 0;
112
+ }}
 
 
113
 
114
+ div[data-testid="stSidebarNav"] li div a:hover {{
115
+ background-color: {self.COLORS['primary'] + '20'};
116
+ }}
117
 
118
+ div[data-testid="stSidebarNav"] li div[aria-selected="true"] {{
119
+ background-color: {self.COLORS['primary'] + '40'};
120
+ }}
121
+
122
+ div[data-testid="stSidebarNav"] li div[aria-selected="true"] a span {{
123
+ color: {self.COLORS['primary']};
124
+ font-weight: 500;
125
+ }}
126
+
127
+ .metric-card {{
128
+ background-color: white;
129
  border-radius: 10px;
130
+ padding: 20px;
131
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
132
+ text-align: center;
133
+ transition: transform 0.3s ease;
134
+ }}
135
 
136
+ .metric-card:hover {{
137
+ transform: translateY(-5px);
138
+ }}
139
+
140
+ .metric-value {{
141
+ font-size: 2.5rem;
142
+ font-weight: 700;
143
+ margin: 10px 0;
144
+ }}
145
+
146
+ .metric-label {{
147
+ font-size: 1rem;
148
+ color: #666;
149
+ }}
150
+
151
+ .metric-change {{
152
+ font-size: 0.9rem;
153
+ margin-top: 5px;
154
+ }}
155
+
156
+ .metric-change-positive {{
157
+ color: {self.COLORS['success']};
158
+ }}
159
+
160
+ .metric-change-negative {{
161
+ color: {self.COLORS['danger']};
162
+ }}
163
+
164
+ .custom-button {{
165
+ background-color: {self.COLORS['primary']};
166
  color: white;
167
+ border: none;
168
+ border-radius: 5px;
169
+ padding: 10px 20px;
170
+ font-size: 1rem;
171
+ cursor: pointer;
172
+ transition: background-color 0.3s ease;
173
+ }}
174
+
175
+ .custom-button:hover {{
176
+ background-color: {self.COLORS['secondary']};
177
+ }}
178
+
179
+ .custom-card {{
180
+ background-color: white;
181
+ border-radius: 10px;
182
+ padding: 20px;
183
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
184
+ margin-bottom: 20px;
185
+ }}
186
 
187
+ .header-container {{
188
+ display: flex;
189
+ justify-content: space-between;
190
+ align-items: center;
191
+ margin-bottom: 2rem;
192
+ padding-bottom: 1rem;
193
+ border-bottom: 1px solid {self.COLORS['border']};
194
+ }}
195
+
196
+ .header-title {{
197
+ color: {self.COLORS['primary']};
198
+ font-size: {self.FONT_SIZES['3xl']};
199
+ margin: 0;
200
+ }}
201
 
202
+ .header-subtitle {{
203
+ color: {self.COLORS['dark']};
204
+ font-size: {self.FONT_SIZES['lg']};
205
+ margin: 0;
206
+ }}
207
 
208
+ .header-actions {{
209
+ display: flex;
210
+ gap: 10px;
211
+ }}
212
 
213
+ /* تنسيق الجداول */
214
+ div[data-testid="stTable"] table {{
215
+ width: 100%;
216
+ border-collapse: collapse;
217
+ }}
218
+
219
+ div[data-testid="stTable"] thead tr th {{
220
+ background-color: {self.COLORS['primary']};
221
  color: white;
222
+ text-align: right;
223
+ padding: 12px;
224
+ }}
225
 
226
+ div[data-testid="stTable"] tbody tr:nth-child(even) {{
227
+ background-color: {self.COLORS['light']};
228
+ }}
 
229
 
230
+ div[data-testid="stTable"] tbody tr:hover {{
231
+ background-color: {self.COLORS['primary'] + '10'};
232
+ }}
 
233
 
234
+ div[data-testid="stTable"] tbody tr td {{
235
+ padding: 10px;
236
+ text-align: right;
237
+ }}
238
+
239
+ /* تنسيق النماذج */
240
+ div[data-testid="stForm"] {{
241
  background-color: white;
242
+ border-radius: 10px;
243
+ padding: 20px;
244
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
245
+ }}
 
 
 
 
 
 
 
 
 
246
 
247
+ button[kind="primaryFormSubmit"] {{
248
+ background-color: {self.COLORS['primary']};
 
 
 
 
 
 
249
  color: white;
250
+ }}
251
 
252
+ button[kind="secondaryFormSubmit"] {{
253
+ background-color: {self.COLORS['light']};
254
+ color: {self.COLORS['dark']};
255
+ border: 1px solid {self.COLORS['border']};
256
+ }}
257
 
258
+ /* تنسيق الرسوم البيانية */
259
+ div[data-testid="stVegaLiteChart"] {{
260
+ background-color: white;
261
+ border-radius: 10px;
262
+ padding: 20px;
263
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
264
+ }}
265
+ """
266
 
267
+ # تطبيق CSS
268
+ st.markdown(f'<style>{css}</style>', unsafe_allow_html=True)
 
 
 
269
 
270
+ def apply_theme_colors(self):
271
+ """تطبيق ألوان السمة الحالية"""
272
+ # تحديد ألوان السمة بناءً على الوضع
273
+ if self.theme_mode == "dark":
274
+ self.COLORS['background'] = '#121212'
275
+ self.COLORS['text'] = '#FFFFFF'
276
+ self.COLORS['border'] = '#333333'
277
+ else:
278
+ self.COLORS['background'] = '#FFFFFF'
279
+ self.COLORS['text'] = '#212121'
280
+ self.COLORS['border'] = '#E0E0E0'
281
+
282
+ # تطبيق CSS للسمة
283
+ theme_css = f"""
284
+ body {{
285
+ background-color: {self.COLORS['background']};
286
+ color: {self.COLORS['text']};
287
+ }}
288
+ """
289
 
290
+ st.markdown(f'<style>{theme_css}</style>', unsafe_allow_html=True)
291
+
292
+ def toggle_theme(self):
293
+ """تبديل وضع السمة بين الفاتح والداكن"""
294
+ if self.theme_mode == "light":
295
+ self.theme_mode = "dark"
296
+ else:
297
+ self.theme_mode = "light"
298
+
299
+ self.apply_theme_colors()
300
 
301
  def create_sidebar(self, menu_items):
302
+ """إنشاء الشريط الجانبي مع قائمة العناصر"""
303
  with st.sidebar:
304
+ # إضافة الشعار
305
  st.markdown(
306
  f"""
307
+ <div style="text-align: center; margin-bottom: 20px;">
308
+ <h2 style="color: {self.COLORS['primary']};">{self.page_icon} {self.page_title}</h2>
 
309
  </div>
310
  """,
311
  unsafe_allow_html=True
312
  )
313
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  # إضافة معلومات المستخدم
 
315
  st.markdown(
316
  f"""
317
+ <div style="text-align: center; margin-bottom: 20px;">
318
+ <div style="width: 60px; height: 60px; border-radius: 50%; background-color: {self.COLORS['primary']}; color: white; display: flex; align-items: center; justify-content: center; margin: 0 auto; font-size: 24px; font-weight: bold;">
319
+ م
320
+ </div>
321
+ <p style="margin-top: 10px; font-weight: bold;">محمد أحمد</p>
322
+ <p style="margin-top: -15px; font-size: 0.8rem; color: #666;">مدير المشاريع</p>
323
  </div>
324
  """,
325
  unsafe_allow_html=True
326
  )
327
 
328
+ st.divider()
329
+
330
+ # إنشاء القائمة
331
+ selected = st.radio(
332
+ "القائمة الرئيسية",
333
+ [item["name"] for item in menu_items],
334
+ format_func=lambda x: x,
335
+ label_visibility="collapsed"
336
+ )
337
+
338
+ st.divider()
339
+
340
+ # إضافة زر تبديل السمة
341
+ if st.button("تبديل السمة 🌓"):
342
+ self.toggle_theme()
343
+ st.experimental_rerun()
344
+
345
+ # إضافة معلومات النظام
346
+ st.markdown(
347
+ """
348
+ <div style="position: absolute; bottom: 20px; left: 20px; right: 20px; text-align: center;">
349
+ <p style="font-size: 0.8rem; color: #666;">نظام تحليل المناقصات | الإصدار 2.0.0</p>
350
+ <p style="font-size: 0.7rem; color: #888;">© 2025 جميع الحقوق محفوظة</p>
351
+ </div>
352
+ """,
353
+ unsafe_allow_html=True
354
+ )
355
+
356
+ return selected
357
 
358
+ def create_header(self, title, subtitle=None, show_actions=True):
359
+ """إنشاء ترويسة الصفحة"""
360
+ col1, col2 = st.columns([3, 1])
361
+
362
+ with col1:
363
+ st.markdown(f'<h1 class="header-title">{title}</h1>', unsafe_allow_html=True)
364
+ if subtitle:
365
+ st.markdown(f'<p class="header-subtitle">{subtitle}</p>', unsafe_allow_html=True)
366
+
367
+ if show_actions:
368
+ with col2:
369
+ st.markdown(
370
+ """
371
+ <div class="header-actions" style="display: flex; justify-content: flex-end; gap: 10px;">
372
+ <button class="custom-button" style="background-color: #43A047;">إضافة جديد</button>
373
+ <button class="custom-button" style="background-color: #1E88E5;">تحديث</button>
374
+ </div>
375
+ """,
376
+ unsafe_allow_html=True
377
+ )
378
+
379
+ st.divider()
380
 
381
+ def create_metric_card(self, label, value, change=None, color=None):
382
+ """إنشاء بطاقة مقياس"""
383
+ if color is None:
384
+ color = self.COLORS['primary']
385
+
386
+ change_html = ""
387
+ if change is not None:
388
+ if change.startswith("+"):
389
+ change_class = "metric-change-positive"
390
+ change_icon = "↑"
391
+ elif change.startswith("-"):
392
+ change_class = "metric-change-negative"
393
+ change_icon = "↓"
394
+ else:
395
+ change_class = ""
396
+ change_icon = ""
397
+
398
+ change_html = f'<div class="metric-change {change_class}">{change_icon} {change}</div>'
399
 
400
  st.markdown(
401
  f"""
402
+ <div class="metric-card" style="border-top: 4px solid {color};">
403
+ <div class="metric-label">{label}</div>
404
+ <div class="metric-value" style="color: {color};">{value}</div>
405
+ {change_html}
406
  </div>
407
  """,
408
  unsafe_allow_html=True
409
  )
410
 
411
+ def create_card(self, title, content, footer=None, color=None):
412
+ """إنشاء بطاقة عامة"""
413
+ if color is None:
414
+ color = self.COLORS['primary']
415
 
416
+ footer_html = ""
417
+ if footer is not None:
418
+ footer_html = f'<div style="margin-top: 15px; padding-top: 10px; border-top: 1px solid {self.COLORS["border"]}; font-size: 0.9rem; color: #666;">{footer}</div>'
419
 
420
  st.markdown(
421
  f"""
422
+ <div class="custom-card" style="border-top: 4px solid {color};">
423
+ <h3 style="color: {color}; margin-top: 0;">{title}</h3>
424
+ <div>{content}</div>
425
+ {footer_html}
426
  </div>
427
  """,
428
  unsafe_allow_html=True
429
  )
430
 
431
+ def create_status_badge(self, status):
432
+ """إنشاء شارة حالة"""
433
+ status_colors = {
434
+ "جديد": self.COLORS['info'],
435
+ "قيد التسعير": self.COLORS['warning'],
436
+ "تم التقديم": self.COLORS['primary'],
437
+ "تمت الترسية": self.COLORS['success'],
438
+ "قيد التنفيذ": self.COLORS['accent'],
439
+ "منتهي": self.COLORS['secondary'],
440
+ "ملغي": self.COLORS['danger']
441
  }
442
 
443
+ color = status_colors.get(status, self.COLORS['primary'])
 
 
 
 
 
444
 
445
+ return f"""
446
+ <span style="background-color: {color}; color: white; padding: 3px 8px; border-radius: 12px; font-size: 0.8rem;">
447
+ {status}
448
+ </span>
449
+ """
450
+
451
+ def create_progress_bar(self, percent, label=None):
452
+ """إنشاء شريط تقدم"""
453
+ if label is None:
454
+ label = f"{percent}%"
455
+
456
+ # تحديد لون شريط التقدم بناءً على النسبة
457
+ if percent < 30:
458
+ color = self.COLORS['danger']
459
+ elif percent < 70:
460
+ color = self.COLORS['warning']
461
+ else:
462
+ color = self.COLORS['success']
463
 
464
  st.markdown(
465
  f"""
466
+ <div style="margin-bottom: 10px;">
467
+ <div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
468
+ <span>{label}</span>
469
+ <span>{percent}%</span>
470
+ </div>
471
+ <div style="background-color: {self.COLORS['light']}; border-radius: 5px; height: 10px;">
472
+ <div style="background-color: {color}; width: {percent}%; height: 100%; border-radius: 5px;"></div>
473
+ </div>
474
  </div>
475
  """,
476
  unsafe_allow_html=True
477
  )
478
 
479
+ def create_tabs_container(self, tabs_data):
480
+ """إنشاء حاوية تبويبات مخصصة"""
481
+ # إنشاء أزرار التبويب
482
+ tab_buttons_html = ""
483
+ for i, tab in enumerate(tabs_data):
484
+ active_class = "active" if i == 0 else ""
485
+ tab_buttons_html += f"""
486
+ <button class="tablinks {active_class}" onclick="openTab(event, 'tab{i}')">{tab['title']}</button>
487
+ """
488
+
489
+ # إنشاء محتوى التبويبات
490
+ tab_content_html = ""
491
+ for i, tab in enumerate(tabs_data):
492
+ display_style = "block" if i == 0 else "none"
493
+ tab_content_html += f"""
494
+ <div id="tab{i}" class="tabcontent" style="display: {display_style};">
495
+ {tab['content']}
496
+ </div>
497
+ """
498
+
499
+ # إنشاء JavaScript للتبديل بين التبويبات
500
+ js = """
501
+ <script>
502
+ function openTab(evt, tabName) {
503
+ var i, tabcontent, tablinks;
504
+ tabcontent = document.getElementsByClassName("tabcontent");
505
+ for (i = 0; i < tabcontent.length; i++) {
506
+ tabcontent[i].style.display = "none";
507
+ }
508
+ tablinks = document.getElementsByClassName("tablinks");
509
+ for (i = 0; i < tablinks.length; i++) {
510
+ tablinks[i].className = tablinks[i].className.replace(" active", "");
511
+ }
512
+ document.getElementById(tabName).style.display = "block";
513
+ evt.currentTarget.className += " active";
514
  }
515
+ </script>
516
+ """
517
+
518
+ # إنشاء CSS للتبويبات
519
+ css = f"""
520
+ <style>
521
+ .tab {{
522
+ overflow: hidden;
523
+ border: 1px solid {self.COLORS['border']};
524
+ background-color: {self.COLORS['light']};
525
+ border-radius: 5px 5px 0 0;
526
+ }}
527
+
528
+ .tab button {{
529
+ background-color: inherit;
530
+ float: right;
531
+ border: none;
532
+ outline: none;
533
+ cursor: pointer;
534
+ padding: 14px 16px;
535
+ transition: 0.3s;
536
+ font-size: 16px;
537
+ font-family: 'Tajawal', sans-serif;
538
+ }}
539
 
540
+ .tab button:hover {{
541
+ background-color: {self.COLORS['primary'] + '20'};
542
+ }}
543
 
544
+ .tab button.active {{
545
+ background-color: {self.COLORS['primary']};
546
+ color: white;
547
+ }}
548
+
549
+ .tabcontent {{
550
+ display: none;
551
+ padding: 20px;
552
+ border: 1px solid {self.COLORS['border']};
553
+ border-top: none;
554
+ border-radius: 0 0 5px 5px;
555
+ }}
556
+ </style>
 
 
 
 
 
557
  """
558
 
559
+ # تجميع HTML
560
+ html = f"""
561
+ {css}
562
+ <div class="tab">
563
+ {tab_buttons_html}
564
+ </div>
565
+
566
+ {tab_content_html}
567
 
568
+ {js}
 
 
569
  """
570
 
571
+ st.markdown(html, unsafe_allow_html=True)
572
 
573
+ def get_icon_html(self, icon_name, size="1rem", color=None):
574
+ """الحصول على HTML لأيقونة Bootstrap"""
575
+ if color is None:
576
+ color = self.COLORS['primary']
 
 
 
 
 
 
 
 
 
 
 
 
577
 
578
+ return f"""
579
+ <i class="bi bi-{icon_name}" style="font-size: {size}; color: {color};"></i>
580
+ """
581
+
582
+ def add_bootstrap_icons(self):
583
+ """إضافة أيقونات Bootstrap"""
584
  st.markdown(
585
+ """
586
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
587
  """,
588
  unsafe_allow_html=True
589
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590
 
591
+ def get_image_base64(self, image_path):
592
+ """تحويل الصورة إلى تنسيق base64"""
593
+ with open(image_path, "rb") as img_file:
594
+ return base64.b64encode(img_file.read()).decode()
 
595
 
596
+ def add_logo(self, logo_path, width="150px"):
597
+ """إضافة شعار"""
598
+ if os.path.exists(logo_path):
599
+ try:
600
+ base64_image = self.get_image_base64(logo_path)
601
+ st.markdown(
602
+ f"""
603
+ <div style="text-align: center; margin-bottom: 20px;">
604
+ <img src="data:image/png;base64,{base64_image}" width="{width}">
605
+ </div>
606
+ """,
607
+ unsafe_allow_html=True
608
+ )
609
+ except Exception as e:
610
+ st.error(f"خطأ في تحميل الشعار: {e}")
611
+ else:
612
+ st.warning(f"ملف الشعار غير موجود: {logo_path}")