Upload 111 files
Browse files- app.py +1109 -779
- database/db_connector.py +3 -23
- modules/ai_assistant/ai_assistant.py +10 -577
- modules/document_comparison/document_comparator.py +0 -29
- modules/pricing/construction_calculator.py +13 -65
- modules/resources/resources_app.py +50 -83
- static/css/backup/enhanced-styles.css +913 -0
- static/css/backup/rtl-fixes.css +57 -0
- static/css/backup/styles.css +373 -0
- static/css/rtl-fixes.css +57 -66
- static/css/unified_design_system.css +488 -0
- static/js/enhanced_main.js +310 -0
- templates/base.html +318 -0
- utils/components/header.py +3 -11
- utils/components/sidebar.py +2 -8
- utils/helpers.py +284 -234
- utils/helpers/__init__.py +1 -31
app.py
CHANGED
@@ -1,923 +1,1253 @@
|
|
1 |
-
#!/usr/bin/env python
|
2 |
-
# -*- coding: utf-8 -*-
|
3 |
-
|
4 |
-
"""
|
5 |
-
نظام واهبي للذكاء الاصطناعي لتحليل العقود والمناقصات
|
6 |
-
تطبيق Streamlit الرئيسي الذي يجمع جميع الوحدات والمكونات
|
7 |
-
"""
|
8 |
-
|
9 |
import os
|
10 |
import sys
|
|
|
|
|
11 |
import streamlit as st
|
12 |
-
import
|
13 |
-
import
|
14 |
|
15 |
-
#
|
16 |
-
|
17 |
-
|
|
|
|
|
|
|
|
|
18 |
try:
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
# قائمة بالحزم المطلوبة
|
23 |
-
required_packages = ['punkt', 'stopwords', 'wordnet']
|
24 |
-
for package in required_packages:
|
25 |
-
try:
|
26 |
-
# محاولة استخدام الحزمة أولاً، وإذا فشلت يتم تنزيلها
|
27 |
-
nltk.data.find(f'tokenizers/{package}')
|
28 |
-
except LookupError:
|
29 |
-
print(f"تنزيل حزمة NLTK: {package}")
|
30 |
-
nltk.download(package, quiet=True)
|
31 |
-
|
32 |
-
print("تم تهيئة موارد NLTK بنجاح.")
|
33 |
-
except Exception as e:
|
34 |
-
print(f"خطأ في تهيئة NLTK: {e}")
|
35 |
|
36 |
-
#
|
37 |
-
|
|
|
|
|
|
|
|
|
|
|
38 |
|
39 |
-
#
|
40 |
-
def
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
# المسار النسبي من مجلد التطبيق (tender-analysis-system)
|
47 |
-
os.path.join(os.path.dirname(__file__), file_path),
|
48 |
-
# المسار النسبي من المجلد الأعلى
|
49 |
-
os.path.join(os.path.dirname(os.path.dirname(__file__)), "tender-analysis-system", file_path),
|
50 |
]
|
51 |
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
|
57 |
-
|
58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
|
60 |
-
#
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
# يحدث هذا غالبًا عند استخدام st.set_page_config أكثر من مرة
|
71 |
|
72 |
-
#
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
#
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
97 |
|
98 |
-
# إضافة Font Awesome
|
99 |
-
|
100 |
-
|
101 |
-
""
|
|
|
102 |
|
103 |
-
#
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
|
|
|
|
|
|
110 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
|
112 |
-
|
113 |
-
|
114 |
-
color: #1E88E5;
|
115 |
-
}
|
116 |
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
-webkit-background-clip: text;
|
127 |
-
-webkit-text-fill-color: transparent;
|
128 |
-
}
|
129 |
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
|
|
|
|
|
|
|
|
|
|
134 |
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
|
|
143 |
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
}
|
154 |
|
155 |
-
|
156 |
-
|
157 |
-
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
158 |
-
}
|
159 |
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
167 |
}
|
168 |
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
173 |
}
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
|
|
|
|
|
|
178 |
}
|
179 |
-
|
180 |
-
|
181 |
-
|
|
|
182 |
background-color: white;
|
183 |
-
border-radius: 10px;
|
184 |
-
padding: 15px;
|
185 |
-
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
186 |
-
margin-bottom: 15px;
|
187 |
-
border-right: 4px solid #1E88E5;
|
188 |
-
}
|
189 |
-
|
190 |
-
.card-title {
|
191 |
-
font-weight: bold;
|
192 |
-
color: #1E88E5;
|
193 |
-
margin-bottom: 10px;
|
194 |
}
|
195 |
-
|
196 |
-
|
197 |
-
display: flex;
|
198 |
-
justify-content: space-between;
|
199 |
}
|
200 |
-
|
201 |
-
|
202 |
-
text-align: center;
|
203 |
}
|
|
|
|
|
204 |
|
205 |
-
|
206 |
-
|
207 |
-
font-size: 1.5rem;
|
208 |
-
}
|
209 |
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
</style>
|
215 |
-
""", unsafe_allow_html=True)
|
216 |
-
|
217 |
-
# استيراد المكونات والوحدات
|
218 |
-
from utils.components.sidebar import render_sidebar
|
219 |
-
from utils.helpers import create_directory_if_not_exists, get_data_folder
|
220 |
-
|
221 |
-
# استيراد وحدات التطبيق
|
222 |
-
from modules.pricing.pricing_app import PricingApp
|
223 |
-
from modules.projects.projects_app import ProjectsApp
|
224 |
-
from modules.resources.resources_app import ResourcesApp
|
225 |
-
from modules.risk_assessment.risk_assessment_app import RiskAssessmentApp
|
226 |
-
from modules.project_tracker.tracker_app import TrackerApp
|
227 |
-
from modules.maps.maps_app import MapsApp
|
228 |
-
from modules.notifications.notifications_app import NotificationsApp
|
229 |
-
from modules.voice_narration.voice_narration_app import VoiceNarrationApp
|
230 |
-
from modules.achievements.achievements_app import AchievementsApp
|
231 |
-
from modules.ai_finetuning.finetuning_app import FinetuningApp
|
232 |
-
from modules.document_comparison.comparison_app import DocumentComparisonApp
|
233 |
-
|
234 |
-
# إنشاء مجلدات البيانات الضرورية
|
235 |
-
create_directory_if_not_exists(get_data_folder())
|
236 |
-
create_directory_if_not_exists(os.path.join(get_data_folder(), "projects"))
|
237 |
-
create_directory_if_not_exists(os.path.join(get_data_folder(), "documents"))
|
238 |
-
create_directory_if_not_exists(os.path.join(get_data_folder(), "analysis"))
|
239 |
-
|
240 |
-
def main():
|
241 |
-
"""الدالة الرئيسية للتطبيق"""
|
242 |
|
243 |
-
#
|
244 |
-
|
245 |
|
246 |
-
|
247 |
-
if "is_authenticated" in st.session_state and not st.session_state.is_authenticated:
|
248 |
-
render_login_screen()
|
249 |
-
return
|
250 |
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
elif selected_module == "التسعير المتكاملة":
|
260 |
-
pricing_app = PricingApp()
|
261 |
-
pricing_app.render()
|
262 |
-
|
263 |
-
elif selected_module == "الموارد والتكاليف":
|
264 |
-
resources_app = ResourcesApp()
|
265 |
-
resources_app.render()
|
266 |
-
|
267 |
-
elif selected_module == "تحليل المستندات":
|
268 |
-
# تقديم واجهة تحليل المستندات
|
269 |
-
render_document_analysis()
|
270 |
-
|
271 |
-
elif selected_module == "مقارنة المستندات":
|
272 |
-
# تقديم واجهة مقارنة المستندات
|
273 |
-
comparison_app = DocumentComparisonApp()
|
274 |
-
comparison_app.render()
|
275 |
-
|
276 |
-
elif selected_module == "تقييم مخاطر العقود":
|
277 |
-
risk_app = RiskAssessmentApp()
|
278 |
-
risk_app.render()
|
279 |
-
|
280 |
-
elif selected_module == "التقارير والتحليلات":
|
281 |
-
# تقديم واجهة التقارير والتحليلات
|
282 |
-
render_reports_and_analytics()
|
283 |
-
|
284 |
-
elif selected_module == "متتبع حالة المشروع":
|
285 |
-
tracker_app = TrackerApp()
|
286 |
-
tracker_app.render()
|
287 |
-
|
288 |
-
elif selected_module == "خريطة المشاريع":
|
289 |
-
maps_app = MapsApp()
|
290 |
-
maps_app.render()
|
291 |
-
|
292 |
-
elif selected_module == "نظام الإشعارات":
|
293 |
-
notifications_app = NotificationsApp()
|
294 |
-
notifications_app.render()
|
295 |
-
|
296 |
-
elif selected_module == "الترجمة الصوتية":
|
297 |
-
voice_app = VoiceNarrationApp()
|
298 |
-
voice_app.render()
|
299 |
-
|
300 |
-
elif selected_module == "نظام الإنجازات":
|
301 |
-
achievements_app = AchievementsApp()
|
302 |
-
achievements_app.render()
|
303 |
-
|
304 |
-
elif selected_module == "المساعد الذكي":
|
305 |
-
# تقديم واجهة المساعد الذكي
|
306 |
-
render_ai_assistant()
|
307 |
-
|
308 |
-
elif selected_module == "ضبط نماذج الذكاء الاصطناعي":
|
309 |
-
finetuning_app = FinetuningApp()
|
310 |
-
finetuning_app.render()
|
311 |
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
<div class="section-card">
|
322 |
-
<h2>تسجيل الدخول</h2>
|
323 |
-
<p>يرجى إدخال بيانات الاعتماد الخاصة بك للوصول إلى النظام.</p>
|
324 |
-
</div>
|
325 |
-
""", unsafe_allow_html=True)
|
326 |
-
|
327 |
-
col1, col2, col3 = st.columns([1, 2, 1])
|
328 |
|
329 |
with col2:
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
st.rerun()
|
347 |
-
else:
|
348 |
-
st.error("اسم المستخدم أو كلمة المرور غير صحيحة")
|
349 |
|
350 |
-
|
351 |
-
<
|
352 |
-
<p>نظام WAHBi للذكاء الاصطناعي © 2025 شركة شبه الجزيرة للمقاولات</p>
|
353 |
-
<p>جميع الحقوق محفوظة</p>
|
354 |
-
</div>
|
355 |
-
""", unsafe_allow_html=True)
|
356 |
-
|
357 |
-
|
358 |
-
def render_homepage():
|
359 |
-
"""عرض الصفحة الرئيسية للتطبيق"""
|
360 |
-
st.markdown("<h1 class='app-title'>نظام WAHBi للذكاء الاصطناعي</h1>", unsafe_allow_html=True)
|
361 |
-
st.markdown("<div style='text-align: center; margin-bottom: 20px;'>نظام متكامل لتحليل العقود والمناقصات باستخدام تقنيات الذكاء الاصطناعي المتقدمة</div>", unsafe_allow_html=True)
|
362 |
|
363 |
-
|
364 |
-
col1, col2, col3, col4 = st.columns(4)
|
365 |
|
366 |
with col1:
|
367 |
st.markdown("""
|
368 |
-
<div class="
|
369 |
-
<div class="
|
370 |
-
<
|
|
|
371 |
</div>
|
372 |
""", unsafe_allow_html=True)
|
373 |
|
374 |
with col2:
|
375 |
st.markdown("""
|
376 |
-
<div class="
|
377 |
-
<div class="
|
378 |
-
<
|
|
|
379 |
</div>
|
380 |
""", unsafe_allow_html=True)
|
381 |
|
382 |
with col3:
|
383 |
st.markdown("""
|
384 |
-
<div class="
|
385 |
-
<div class="
|
386 |
-
<
|
|
|
387 |
</div>
|
388 |
""", unsafe_allow_html=True)
|
389 |
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
|
|
|
|
|
|
|
|
397 |
|
398 |
-
#
|
399 |
-
col1, col2 = st.columns([
|
400 |
|
401 |
with col1:
|
402 |
-
st.
|
403 |
-
|
404 |
-
st.markdown("""
|
405 |
-
<div class="card">
|
406 |
-
<div class="card-title">إنشاء طريق سريع بمنطقة الرياض</div>
|
407 |
-
<div>رقم المناقصة: TR-2025-134</div>
|
408 |
-
<div>الجهة المالكة: وزارة النقل</div>
|
409 |
-
<div>تاريخ الإغلاق: 15 أبريل 2025</div>
|
410 |
-
<div class="card-metrics" style="margin-top: 10px;">
|
411 |
-
<div class="card-metric">
|
412 |
-
<div class="card-metric-value" style="color: #4CAF50;">85%</div>
|
413 |
-
<div class="card-metric-label">نسبة الإنجاز</div>
|
414 |
-
</div>
|
415 |
-
<div class="card-metric">
|
416 |
-
<div class="card-metric-value" style="color: #FFC107;">متوسطة</div>
|
417 |
-
<div class="card-metric-label">المخاطر</div>
|
418 |
-
</div>
|
419 |
-
<div class="card-metric">
|
420 |
-
<div class="card-metric-value" style="color: #2196F3;">مرتفعة</div>
|
421 |
-
<div class="card-metric-label">الأولوية</div>
|
422 |
-
</div>
|
423 |
-
</div>
|
424 |
-
</div>
|
425 |
-
|
426 |
-
<div class="card">
|
427 |
-
<div class="card-title">تطوير شبكة الصرف الصحي بالمنطقة الشرقية</div>
|
428 |
-
<div>رقم المناقصة: WS-2025-089</div>
|
429 |
-
<div>الجهة المالكة: وزارة المياه</div>
|
430 |
-
<div>تاريخ الإغلاق: 22 أبريل 2025</div>
|
431 |
-
<div class="card-metrics" style="margin-top: 10px;">
|
432 |
-
<div class="card-metric">
|
433 |
-
<div class="card-metric-value" style="color: #4CAF50;">62%</div>
|
434 |
-
<div class="card-metric-label">نسبة الإنجاز</div>
|
435 |
-
</div>
|
436 |
-
<div class="card-metric">
|
437 |
-
<div class="card-metric-value" style="color: #F44336;">مرتفعة</div>
|
438 |
-
<div class="card-metric-label">المخاطر</div>
|
439 |
-
</div>
|
440 |
-
<div class="card-metric">
|
441 |
-
<div class="card-metric-value" style="color: #2196F3;">مرت��عة</div>
|
442 |
-
<div class="card-metric-label">الأولوية</div>
|
443 |
-
</div>
|
444 |
-
</div>
|
445 |
-
</div>
|
446 |
-
|
447 |
-
<div class="card">
|
448 |
-
<div class="card-title">بناء 3 مدارس بمنطقة مكة المكرمة</div>
|
449 |
-
<div>رقم المناقصة: ED-2025-112</div>
|
450 |
-
<div>الجهة المالكة: وزارة التعليم</div>
|
451 |
-
<div>تاريخ الإغلاق: 5 مايو 2025</div>
|
452 |
-
<div class="card-metrics" style="margin-top: 10px;">
|
453 |
-
<div class="card-metric">
|
454 |
-
<div class="card-metric-value" style="color: #4CAF50;">38%</div>
|
455 |
-
<div class="card-metric-label">نسبة الإنجاز</div>
|
456 |
-
</div>
|
457 |
-
<div class="card-metric">
|
458 |
-
<div class="card-metric-value" style="color: #4CAF50;">منخفضة</div>
|
459 |
-
<div class="card-metric-label">المخاطر</div>
|
460 |
-
</div>
|
461 |
-
<div class="card-metric">
|
462 |
-
<div class="card-metric-value" style="color: #FFC107;">متوسطة</div>
|
463 |
-
<div class="card-metric-label">الأولوية</div>
|
464 |
-
</div>
|
465 |
-
</div>
|
466 |
-
</div>
|
467 |
-
""", unsafe_allow_html=True)
|
468 |
|
469 |
with col2:
|
470 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
471 |
|
472 |
-
|
473 |
-
|
474 |
-
|
475 |
-
|
476 |
-
|
477 |
-
|
478 |
-
|
479 |
-
|
480 |
-
|
481 |
-
|
482 |
-
|
483 |
-
|
484 |
-
|
485 |
-
|
486 |
-
<button style="background-color: #8E24AA; color: white; border: none; border-radius: 5px; padding: 10px; text-align: right; cursor: pointer; font-weight: bold;">
|
487 |
-
<i class="fas fa-map-marker-alt" style="margin-left: 10px;"></i> استعراض خر��طة المشاريع
|
488 |
-
</button>
|
489 |
-
|
490 |
-
<button style="background-color: #546E7A; color: white; border: none; border-radius: 5px; padding: 10px; text-align: right; cursor: pointer; font-weight: bold;">
|
491 |
-
<i class="fas fa-chart-bar" style="margin-left: 10px;"></i> إنشاء تقارير تحليلية
|
492 |
-
</button>
|
493 |
-
</div>
|
494 |
-
""", unsafe_allow_html=True)
|
495 |
|
496 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
497 |
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
503 |
|
504 |
-
|
505 |
-
|
506 |
-
|
507 |
-
|
|
|
|
|
|
|
|
|
508 |
|
509 |
-
|
510 |
-
|
511 |
-
<div style="font-size: 0.9rem;">اكتمل تحليل عقد بناء المدارس بنجاح</div>
|
512 |
-
</div>
|
513 |
-
""", unsafe_allow_html=True)
|
514 |
|
515 |
-
|
516 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
517 |
|
518 |
-
|
519 |
-
|
520 |
-
|
521 |
-
|
522 |
-
|
523 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
524 |
|
525 |
-
#
|
526 |
st.markdown("""
|
527 |
-
<
|
528 |
-
<p
|
529 |
-
|
530 |
-
</div>
|
531 |
""", unsafe_allow_html=True)
|
532 |
|
533 |
-
|
534 |
-
def
|
535 |
-
|
536 |
-
st.markdown("<h1 class='
|
537 |
|
|
|
538 |
st.markdown("""
|
539 |
-
<div class="
|
540 |
-
<
|
541 |
-
|
|
|
542 |
</div>
|
543 |
""", unsafe_allow_html=True)
|
544 |
|
545 |
-
#
|
546 |
-
st.markdown("
|
547 |
|
548 |
-
|
|
|
549 |
|
550 |
with col1:
|
551 |
-
st.
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
<button style="background-color: #1E88E5; color: white; border: none; border-radius: 5px; padding: 8px 12px; cursor: pointer; width: 100%;">
|
556 |
-
تحليل جديد
|
557 |
-
</button>
|
558 |
-
</div>
|
559 |
-
""", unsafe_allow_html=True)
|
560 |
|
561 |
with col2:
|
562 |
-
st.
|
563 |
-
|
564 |
-
|
565 |
-
|
566 |
-
<button style="background-color: #1E88E5; color: white; border: none; border-radius: 5px; padding: 8px 12px; cursor: pointer; width: 100%;">
|
567 |
-
تحليل جديد
|
568 |
-
</button>
|
569 |
-
</div>
|
570 |
-
""", unsafe_allow_html=True)
|
571 |
|
572 |
-
|
573 |
-
|
574 |
-
|
575 |
-
|
576 |
-
|
577 |
-
|
578 |
-
|
579 |
-
|
580 |
-
|
581 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
582 |
|
583 |
-
#
|
584 |
-
st.markdown("
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
585 |
|
|
|
586 |
st.markdown("""
|
587 |
-
<
|
588 |
-
<
|
589 |
-
<
|
590 |
-
|
591 |
-
|
592 |
-
<th style="padding: 12px; text-align: right; border-bottom: 2px solid #ddd;">تاريخ التحليل</th>
|
593 |
-
<th style="padding: 12px; text-align: right; border-bottom: 2px solid #ddd;">الحالة</th>
|
594 |
-
<th style="padding: 12px; text-align: right; border-bottom: 2px solid #ddd;">الإجراءات</th>
|
595 |
-
</tr>
|
596 |
-
</thead>
|
597 |
-
<tbody>
|
598 |
-
<tr>
|
599 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">عقد إنشاء طريق سريع.pdf</td>
|
600 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">تحليل شامل</td>
|
601 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">2023-03-25</td>
|
602 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;"><span style="color: #4CAF50; font-weight: bold;">مكتمل</span></td>
|
603 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">
|
604 |
-
<button style="background-color: #1E88E5; color: white; border: none; border-radius: 3px; padding: 5px 10px; margin-left: 5px; cursor: pointer;">عرض</button>
|
605 |
-
<button style="background-color: #78909C; color: white; border: none; border-radius: 3px; padding: 5px 10px; cursor: pointer;">تنزيل التقرير</button>
|
606 |
-
</td>
|
607 |
-
</tr>
|
608 |
-
<tr style="background-color: #f9f9f9;">
|
609 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">جداول كميات مشروع صرف صحي.xlsx</td>
|
610 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">تحليل جداول الكميات</td>
|
611 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">2023-03-23</td>
|
612 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;"><span style="color: #4CAF50; font-weight: bold;">مكتمل</span></td>
|
613 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">
|
614 |
-
<button style="background-color: #1E88E5; color: white; border: none; border-radius: 3px; padding: 5px 10px; margin-left: 5px; cursor: pointer;">عرض</button>
|
615 |
-
<button style="background-color: #78909C; color: white; border: none; border-radius: 3px; padding: 5px 10px; cursor: pointer;">تنزيل التقرير</button>
|
616 |
-
</td>
|
617 |
-
</tr>
|
618 |
-
<tr>
|
619 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">شروط وأحكام عقد بناء مدارس.pdf</td>
|
620 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">تحليل الشروط والأحكام</td>
|
621 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">2023-03-20</td>
|
622 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;"><span style="color: #4CAF50; font-weight: bold;">مكتمل</span></td>
|
623 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">
|
624 |
-
<button style="background-color: #1E88E5; color: white; border: none; border-radius: 3px; padding: 5px 10px; margin-left: 5px; cursor: pointer;">عرض</button>
|
625 |
-
<button style="background-color: #78909C; color: white; border: none; border-radius: 3px; padding: 5px 10px; cursor: pointer;">تنزيل التقرير</button>
|
626 |
-
</td>
|
627 |
-
</tr>
|
628 |
-
<tr style="background-color: #f9f9f9;">
|
629 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">ملحق عقد مشروع كباري.pdf</td>
|
630 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">تحليل شامل</td>
|
631 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">2023-03-18</td>
|
632 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;"><span style="color: #FB8C00; font-weight: bold;">قيد المعالجة</span></td>
|
633 |
-
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">
|
634 |
-
<button style="background-color: #9E9E9E; color: white; border: none; border-radius: 3px; padding: 5px 10px; margin-left: 5px; cursor: pointer;" disabled>عرض</button>
|
635 |
-
<button style="background-color: #9E9E9E; color: white; border: none; border-radius: 3px; padding: 5px 10px; cursor: pointer;" disabled>تنزيل التقرير</button>
|
636 |
-
</td>
|
637 |
-
</tr>
|
638 |
-
</tbody>
|
639 |
-
</table>
|
640 |
""", unsafe_allow_html=True)
|
641 |
|
642 |
-
#
|
643 |
-
st.markdown("
|
644 |
|
|
|
645 |
col1, col2 = st.columns(2)
|
646 |
|
647 |
with col1:
|
648 |
-
st.
|
649 |
-
|
650 |
-
|
651 |
-
|
652 |
-
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
|
653 |
-
<span>عقود ومناقصات</span>
|
654 |
-
<span>45%</span>
|
655 |
-
</div>
|
656 |
-
<div style="height: 10px; background-color: #e0e0e0; border-radius: 5px;">
|
657 |
-
<div style="height: 100%; width: 45%; background-color: #1E88E5; border-radius: 5px;"></div>
|
658 |
-
</div>
|
659 |
-
</div>
|
660 |
-
|
661 |
-
<div style="margin-bottom: 15px;">
|
662 |
-
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
|
663 |
-
<span>جداول كميات</span>
|
664 |
-
<span>30%</span>
|
665 |
-
</div>
|
666 |
-
<div style="height: 10px; background-color: #e0e0e0; border-radius: 5px;">
|
667 |
-
<div style="height: 100%; width: 30%; background-color: #43A047; border-radius: 5px;"></div>
|
668 |
-
</div>
|
669 |
-
</div>
|
670 |
-
|
671 |
-
<div style="margin-bottom: 15px;">
|
672 |
-
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
|
673 |
-
<span>شروط وأحكام</span>
|
674 |
-
<span>15%</span>
|
675 |
-
</div>
|
676 |
-
<div style="height: 10px; background-color: #e0e0e0; border-radius: 5px;">
|
677 |
-
<div style="height: 100%; width: 15%; background-color: #FB8C00; border-radius: 5px;"></div>
|
678 |
-
</div>
|
679 |
-
</div>
|
680 |
-
|
681 |
-
<div style="margin-bottom: 15px;">
|
682 |
-
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
|
683 |
-
<span>مستندات أخرى</span>
|
684 |
-
<span>10%</span>
|
685 |
-
</div>
|
686 |
-
<div style="height: 10px; background-color: #e0e0e0; border-radius: 5px;">
|
687 |
-
<div style="height: 100%; width: 10%; background-color: #78909C; border-radius: 5px;"></div>
|
688 |
-
</div>
|
689 |
-
</div>
|
690 |
-
</div>
|
691 |
-
""", unsafe_allow_html=True)
|
692 |
|
693 |
with col2:
|
694 |
-
st.
|
695 |
-
|
696 |
-
|
697 |
-
|
698 |
-
|
699 |
-
|
700 |
-
|
701 |
-
|
702 |
-
|
703 |
-
|
704 |
-
|
705 |
-
|
706 |
-
|
707 |
-
|
708 |
-
|
709 |
-
|
710 |
-
|
|
|
|
|
|
|
|
|
|
|
711 |
|
712 |
-
|
713 |
-
|
714 |
-
<div style="width: 150px;">تحليل شامل:</div>
|
715 |
-
<div style="flex-grow: 1; height: 8px; background-color: #e0e0e0; border-radius: 5px;">
|
716 |
-
<div style="height: 100%; width: 80%; background-color: #1E88E5; border-radius: 5px;"></div>
|
717 |
-
</div>
|
718 |
-
<div style="width: 50px; text-align: left; padding-left: 10px;">2:30</div>
|
719 |
-
</div>
|
720 |
|
721 |
-
|
722 |
-
|
723 |
-
<div style="flex-grow: 1; height: 8px; background-color: #e0e0e0; border-radius: 5px;">
|
724 |
-
<div style="height: 100%; width: 50%; background-color: #43A047; border-radius: 5px;"></div>
|
725 |
-
</div>
|
726 |
-
<div style="width: 50px; text-align: left; padding-left: 10px;">1:45</div>
|
727 |
-
</div>
|
728 |
|
729 |
-
|
730 |
-
|
731 |
-
|
732 |
-
|
733 |
-
|
734 |
-
|
735 |
-
|
736 |
-
|
737 |
-
|
738 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
739 |
|
740 |
-
|
741 |
-
|
742 |
-
|
|
|
743 |
|
|
|
744 |
st.markdown("""
|
745 |
-
<div class="
|
746 |
-
<
|
747 |
-
|
|
|
748 |
</div>
|
749 |
""", unsafe_allow_html=True)
|
750 |
|
751 |
-
#
|
752 |
-
st.markdown("
|
753 |
|
754 |
col1, col2, col3 = st.columns(3)
|
755 |
|
756 |
with col1:
|
757 |
-
st.
|
758 |
-
|
759 |
-
|
760 |
-
|
761 |
-
<button style="background-color: #1E88E5; color: white; border: none; border-radius: 5px; padding: 8px 12px; cursor: pointer; width: 100%;">
|
762 |
-
إنشاء تقرير
|
763 |
-
</button>
|
764 |
-
</div>
|
765 |
-
""", unsafe_allow_html=True)
|
766 |
|
767 |
with col2:
|
768 |
-
st.
|
769 |
-
|
770 |
-
|
771 |
-
|
772 |
-
|
773 |
-
إنشاء تقرير
|
774 |
-
</button>
|
775 |
-
</div>
|
776 |
-
""", unsafe_allow_html=True)
|
777 |
|
778 |
with col3:
|
779 |
-
st.
|
780 |
-
|
781 |
-
|
782 |
-
|
783 |
-
|
784 |
-
إنشاء تقرير
|
785 |
-
</button>
|
786 |
-
</div>
|
787 |
-
""", unsafe_allow_html=True)
|
788 |
|
789 |
-
#
|
790 |
-
st.
|
791 |
|
792 |
-
|
|
|
793 |
|
794 |
-
|
795 |
-
|
796 |
-
|
797 |
-
|
798 |
-
|
799 |
-
|
800 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
801 |
</div>
|
802 |
-
|
|
|
803 |
|
804 |
-
|
805 |
-
|
806 |
-
|
807 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
808 |
|
809 |
-
|
810 |
-
|
811 |
-
<span>نسبة المشاريع المتأخرة</span>
|
812 |
-
<span style="color: #F44336; font-weight: bold;">15%</span>
|
813 |
-
</div>
|
814 |
-
<div style="height: 8px; background-color: #e0e0e0; border-radius: 5px;">
|
815 |
-
<div style="height: 100%; width: 15%; background-color: #F44336; border-radius: 5px;"></div>
|
816 |
-
</div>
|
817 |
-
</div>
|
818 |
|
819 |
-
|
820 |
-
|
821 |
-
|
822 |
-
<
|
823 |
</div>
|
824 |
-
<div
|
825 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
826 |
</div>
|
827 |
</div>
|
|
|
828 |
|
829 |
-
|
830 |
-
|
831 |
-
|
832 |
-
|
833 |
-
|
834 |
-
|
835 |
-
|
836 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
837 |
</div>
|
838 |
</div>
|
839 |
-
|
840 |
-
|
841 |
-
|
842 |
-
|
843 |
-
|
844 |
-
st.markdown("""
|
845 |
-
<div style="padding: 20px; background-color: #f5f5f5; border-radius: 10px; margin-bottom: 20px;">
|
846 |
-
<h4 style="color: #1E88E5; margin-bottom: 15px;">التقرير الشهري لمشاريع الربع الأول 2025</h4>
|
847 |
-
<p>تقرير شامل يوضح أداء جميع المشاريع النشطة خلال الربع الأول من عام 2025، بما في ذلك تحليل التكاليف والجدول الزمني والمخاطر.</p>
|
848 |
-
<div style="display: flex; justify-content: space-between; margin-top: 10px;">
|
849 |
-
<div>
|
850 |
-
<span style="color: #666;">تاريخ الإنشاء: </span>
|
851 |
-
<span>15 مارس 2025</span>
|
852 |
</div>
|
853 |
-
<div>
|
854 |
-
<
|
855 |
-
<button
|
856 |
</div>
|
857 |
</div>
|
858 |
-
|
859 |
|
860 |
-
|
861 |
-
<
|
862 |
-
|
863 |
-
|
864 |
-
|
865 |
-
|
866 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
867 |
</div>
|
868 |
-
<div>
|
869 |
-
<
|
870 |
-
<button
|
871 |
</div>
|
872 |
</div>
|
873 |
-
|
874 |
-
|
875 |
-
|
876 |
-
|
877 |
-
<p>تقرير تفصيلي حول المخاطر المالية للمشاريع الجارية، بما في ذلك تحليل التدفقات النقدية والمستحقات المتأخرة والمطالبات المحتملة.</p>
|
878 |
-
<div style="display: flex; justify-content: space-between; margin-top: 10px;">
|
879 |
-
<div>
|
880 |
-
<span style="color: #666;">تاريخ الإنشاء: </span>
|
881 |
-
<span>10 فبراير 2025</span>
|
882 |
</div>
|
883 |
-
<div>
|
884 |
-
<
|
885 |
-
<button
|
886 |
</div>
|
887 |
</div>
|
888 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
889 |
""", unsafe_allow_html=True)
|
890 |
|
891 |
-
|
892 |
-
def
|
893 |
-
|
894 |
-
|
895 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
896 |
|
897 |
-
#
|
898 |
-
st.markdown("<
|
|
|
|
|
|
|
|
|
|
|
899 |
|
900 |
-
|
901 |
-
<
|
902 |
-
|
903 |
-
|
904 |
-
|
905 |
-
|
|
|
906 |
|
907 |
-
#
|
908 |
-
|
909 |
-
ai_assistant.render()
|
910 |
|
911 |
-
|
912 |
-
st.
|
913 |
-
|
914 |
-
|
915 |
-
|
916 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
917 |
</div>
|
918 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
919 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
920 |
|
921 |
-
# تشغيل التطبيق
|
922 |
if __name__ == "__main__":
|
923 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import os
|
2 |
import sys
|
3 |
+
from datetime import datetime
|
4 |
+
import locale
|
5 |
import streamlit as st
|
6 |
+
from PIL import Image, ImageDraw, ImageFont
|
7 |
+
import io
|
8 |
|
9 |
+
# إضافة مسار المشروع إلى مسار النظام
|
10 |
+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
11 |
+
|
12 |
+
# تعيين اللغة العربية للتاريخ
|
13 |
+
try:
|
14 |
+
locale.setlocale(locale.LC_TIME, 'ar_SA.UTF-8')
|
15 |
+
except:
|
16 |
try:
|
17 |
+
locale.setlocale(locale.LC_TIME, 'ar_SA')
|
18 |
+
except:
|
19 |
+
pass # استخدام اللغة الافتراضية إذا لم تكن العربية متاحة
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
|
21 |
+
# تكوين الصفحة
|
22 |
+
st.set_page_config(
|
23 |
+
page_title="نظام تحليل المناقصات",
|
24 |
+
page_icon="📊",
|
25 |
+
layout="wide",
|
26 |
+
initial_sidebar_state="expanded"
|
27 |
+
)
|
28 |
|
29 |
+
# إنشاء مجلدات الصور إذا لم تكن موجودة
|
30 |
+
def ensure_directories():
|
31 |
+
directories = [
|
32 |
+
os.path.join(os.path.dirname(__file__), "static"),
|
33 |
+
os.path.join(os.path.dirname(__file__), "static/css"),
|
34 |
+
os.path.join(os.path.dirname(__file__), "static/js"),
|
35 |
+
os.path.join(os.path.dirname(__file__), "static/images"),
|
|
|
|
|
|
|
|
|
36 |
]
|
37 |
|
38 |
+
for directory in directories:
|
39 |
+
if not os.path.exists(directory):
|
40 |
+
try:
|
41 |
+
os.makedirs(directory, exist_ok=True)
|
42 |
+
st.sidebar.success(f"تم إنشاء المجلد: {directory}")
|
43 |
+
except Exception as e:
|
44 |
+
st.sidebar.error(f"خطأ في إنشاء المجلد {directory}: {str(e)}")
|
45 |
+
|
46 |
+
# إنشاء ملف الشعار إذا لم يكن موجوداً
|
47 |
+
def create_default_logo():
|
48 |
+
images_dir = os.path.join(os.path.dirname(__file__), "static/images")
|
49 |
+
logo_path = os.path.join(images_dir, "logo.png")
|
50 |
|
51 |
+
if not os.path.exists(logo_path):
|
52 |
+
try:
|
53 |
+
# إنشاء صورة بسيطة للشعار
|
54 |
+
img = Image.new('RGBA', (200, 200), color=(255, 255, 255, 0))
|
55 |
+
d = ImageDraw.Draw(img)
|
56 |
+
|
57 |
+
# رسم دائرة زرقاء
|
58 |
+
d.ellipse((40, 40, 160, 160), fill=(37, 99, 235))
|
59 |
+
|
60 |
+
# إضافة حرف "م" باللون الأبيض
|
61 |
+
try:
|
62 |
+
# محاولة استخدام خط مناسب للعربية إذا كان متاحاً
|
63 |
+
font = ImageFont.truetype("Arial", 80)
|
64 |
+
except:
|
65 |
+
# استخدام الخط الافتراضي إذا لم يكن متاحاً
|
66 |
+
font = ImageFont.load_default()
|
67 |
+
|
68 |
+
d.text((100, 100), "م", fill=(255, 255, 255), font=font, anchor="mm")
|
69 |
+
|
70 |
+
# حفظ الصورة
|
71 |
+
img.save(logo_path)
|
72 |
+
st.sidebar.success(f"تم إنشاء شعار افتراضي: {logo_path}")
|
73 |
+
except Exception as e:
|
74 |
+
st.sidebar.error(f"خطأ في إنشاء الشعار الافتراضي: {str(e)}")
|
75 |
|
76 |
+
# تضمين ملفات CSS
|
77 |
+
def load_css():
|
78 |
+
css_file = os.path.join(os.path.dirname(__file__), "static/css/unified_design_system.css")
|
79 |
+
if os.path.exists(css_file):
|
80 |
+
with open(css_file, "r", encoding="utf-8") as f:
|
81 |
+
st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
|
82 |
+
else:
|
83 |
+
st.warning(f"ملف CSS غير موجود: {css_file}")
|
84 |
+
# إنشاء ملف CSS افتراضي
|
85 |
+
create_default_css()
|
|
|
86 |
|
87 |
+
# إنشاء ملف CSS افتراضي إذا لم يكن موجوداً
|
88 |
+
def create_default_css():
|
89 |
+
css_dir = os.path.join(os.path.dirname(__file__), "static/css")
|
90 |
+
css_file = os.path.join(css_dir, "unified_design_system.css")
|
91 |
+
|
92 |
+
if not os.path.exists(css_file):
|
93 |
+
try:
|
94 |
+
# ملف CSS أساسي
|
95 |
+
basic_css = """
|
96 |
+
/* نظام تصميم موحد أساسي */
|
97 |
+
@import url('https://fonts.googleapis.com/css2?family=Almarai:wght@300;400;700;800&display=swap');
|
98 |
+
|
99 |
+
:root {
|
100 |
+
--primary-color: #2563eb;
|
101 |
+
--primary-light: #dbeafe;
|
102 |
+
--primary-dark: #1e40af;
|
103 |
+
--text-dark: #1e293b;
|
104 |
+
--text-medium: #475569;
|
105 |
+
--background-light: #f8fafc;
|
106 |
+
--background-white: #ffffff;
|
107 |
+
--border-color: #e2e8f0;
|
108 |
+
--success-color: #22c55e;
|
109 |
+
--warning-color: #f59e0b;
|
110 |
+
--danger-color: #ef4444;
|
111 |
+
--info-color: #3b82f6;
|
112 |
+
}
|
113 |
+
|
114 |
+
body {
|
115 |
+
font-family: 'Almarai', sans-serif;
|
116 |
+
color: var(--text-dark);
|
117 |
+
background-color: var(--background-light);
|
118 |
+
direction: rtl;
|
119 |
+
text-align: right;
|
120 |
+
}
|
121 |
+
|
122 |
+
.stApp {
|
123 |
+
direction: rtl;
|
124 |
+
}
|
125 |
+
"""
|
126 |
+
|
127 |
+
with open(css_file, "w", encoding="utf-8") as f:
|
128 |
+
f.write(basic_css)
|
129 |
+
|
130 |
+
st.sidebar.success(f"تم إنشاء ملف CSS افتراضي: {css_file}")
|
131 |
+
except Exception as e:
|
132 |
+
st.sidebar.error(f"خطأ في إنشاء ملف CSS الافتراضي: {str(e)}")
|
133 |
|
134 |
+
# إضافة Font Awesome
|
135 |
+
def load_font_awesome():
|
136 |
+
st.markdown("""
|
137 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
138 |
+
""", unsafe_allow_html=True)
|
139 |
|
140 |
+
# دالة للحصول على أيقونة مناسبة لكل عنصر قائمة
|
141 |
+
def get_icon(menu_item):
|
142 |
+
icons = {
|
143 |
+
"الرئيسية": "home",
|
144 |
+
"المناقصات": "file-contract",
|
145 |
+
"التحليلات": "chart-line",
|
146 |
+
"التقارير": "file-alt",
|
147 |
+
"المساعد الذكي": "robot",
|
148 |
+
"استخراج المستندات": "file-import",
|
149 |
+
"الإعدادات": "cog"
|
150 |
}
|
151 |
+
return icons.get(menu_item, "circle")
|
152 |
+
|
153 |
+
# دالة عرض لوحة التحكم
|
154 |
+
def show_dashboard():
|
155 |
+
# ترويسة الصفحة
|
156 |
+
st.markdown("<h1 class='main-title'>لوحة التحكم</h1>", unsafe_allow_html=True)
|
157 |
|
158 |
+
# إحصائيات لوحة التحكم
|
159 |
+
col1, col2, col3, col4 = st.columns(4)
|
|
|
|
|
160 |
|
161 |
+
with col1:
|
162 |
+
st.markdown("""
|
163 |
+
<div class="stat-card">
|
164 |
+
<div class="stat-icon"><i class="fas fa-file-contract"></i></div>
|
165 |
+
<div class="stat-value">156</div>
|
166 |
+
<div class="stat-label">إجمالي المناقصات</div>
|
167 |
+
<div class="stat-change positive"><i class="fas fa-arrow-up"></i> 12% منذ الشهر الماضي</div>
|
168 |
+
</div>
|
169 |
+
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
170 |
|
171 |
+
with col2:
|
172 |
+
st.markdown("""
|
173 |
+
<div class="stat-card">
|
174 |
+
<div class="stat-icon"><i class="fas fa-check-circle"></i></div>
|
175 |
+
<div class="stat-value">89</div>
|
176 |
+
<div class="stat-label">مناقصات مكتملة</div>
|
177 |
+
<div class="stat-change positive"><i class="fas fa-arrow-up"></i> 8% منذ الشهر الماضي</div>
|
178 |
+
</div>
|
179 |
+
""", unsafe_allow_html=True)
|
180 |
|
181 |
+
with col3:
|
182 |
+
st.markdown("""
|
183 |
+
<div class="stat-card">
|
184 |
+
<div class="stat-icon"><i class="fas fa-spinner"></i></div>
|
185 |
+
<div class="stat-value">42</div>
|
186 |
+
<div class="stat-label">قيد التحليل</div>
|
187 |
+
<div class="stat-change negative"><i class="fas fa-arrow-down"></i> 5% منذ الشهر الماضي</div>
|
188 |
+
</div>
|
189 |
+
""", unsafe_allow_html=True)
|
190 |
|
191 |
+
with col4:
|
192 |
+
st.markdown("""
|
193 |
+
<div class="stat-card">
|
194 |
+
<div class="stat-icon"><i class="fas fa-money-bill-wave"></i></div>
|
195 |
+
<div class="stat-value">25.4M</div>
|
196 |
+
<div class="stat-label">إجمالي القيمة (ريال)</div>
|
197 |
+
<div class="stat-change positive"><i class="fas fa-arrow-up"></i> 15% منذ الشهر الماضي</div>
|
198 |
+
</div>
|
199 |
+
""", unsafe_allow_html=True)
|
|
|
200 |
|
201 |
+
# المناقصات الأخيرة
|
202 |
+
st.markdown("<h2 class='module-title'>المناقصات الأخيرة</h2>", unsafe_allow_html=True)
|
|
|
|
|
203 |
|
204 |
+
# بيانات المناقصات
|
205 |
+
data = {
|
206 |
+
"رقم": [1, 2, 3, 4, 5],
|
207 |
+
"اسم المناقصة": [
|
208 |
+
"مشروع تطوير البنية التحتية",
|
209 |
+
"توريد معدات طبية",
|
210 |
+
"بناء مدرسة ثانوية",
|
211 |
+
"صيانة طرق",
|
212 |
+
"تطوير نظام إلكتروني"
|
213 |
+
],
|
214 |
+
"الجهة": [
|
215 |
+
"وزارة النقل",
|
216 |
+
"وزارة الصحة",
|
217 |
+
"وزارة التعليم",
|
218 |
+
"وزارة النقل",
|
219 |
+
"وزارة الداخلية"
|
220 |
+
],
|
221 |
+
"التاريخ": [
|
222 |
+
"2025/03/15",
|
223 |
+
"2025/03/20",
|
224 |
+
"2025/03/25",
|
225 |
+
"2025/03/28",
|
226 |
+
"2025/04/01"
|
227 |
+
],
|
228 |
+
"الحالة": [
|
229 |
+
"مكتملة",
|
230 |
+
"قيد التحليل",
|
231 |
+
"جديدة",
|
232 |
+
"مكتملة",
|
233 |
+
"جديدة"
|
234 |
+
]
|
235 |
}
|
236 |
|
237 |
+
# تطبيق أنماط CSS على الجدول
|
238 |
+
st.markdown("""
|
239 |
+
<style>
|
240 |
+
.dataframe {
|
241 |
+
width: 100%;
|
242 |
+
border-collapse: separate;
|
243 |
+
border-spacing: 0;
|
244 |
+
margin-bottom: 1.5rem;
|
245 |
+
border-radius: var(--border-radius-lg, 0.75rem);
|
246 |
+
overflow: hidden;
|
247 |
+
box-shadow: var(--shadow-md, 0 4px 6px -1px rgba(0, 0, 0, 0.1));
|
248 |
}
|
249 |
+
.dataframe th {
|
250 |
+
background-color: var(--primary-color, #2563eb);
|
251 |
+
color: white;
|
252 |
+
text-align: right;
|
253 |
+
padding: 0.75rem 1rem;
|
254 |
+
font-weight: 600;
|
255 |
+
border: none;
|
256 |
}
|
257 |
+
.dataframe td {
|
258 |
+
padding: 0.75rem 1rem;
|
259 |
+
border-bottom: 1px solid var(--border-color, #e2e8f0);
|
260 |
+
text-align: right;
|
261 |
background-color: white;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
262 |
}
|
263 |
+
.dataframe tr:nth-child(even) td {
|
264 |
+
background-color: rgba(248, 249, 250, 0.7);
|
|
|
|
|
265 |
}
|
266 |
+
.dataframe tr:hover td {
|
267 |
+
background-color: var(--primary-light, #dbeafe);
|
|
|
268 |
}
|
269 |
+
</style>
|
270 |
+
""", unsafe_allow_html=True)
|
271 |
|
272 |
+
# عرض الجدول
|
273 |
+
st.dataframe(data)
|
|
|
|
|
274 |
|
275 |
+
# زر عرض الكل
|
276 |
+
col1, col2, col3 = st.columns([2, 2, 1])
|
277 |
+
with col3:
|
278 |
+
st.button("عرض كل المناقصات", type="primary")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
279 |
|
280 |
+
# تحليلات المناقصات
|
281 |
+
st.markdown("<h2 class='module-title'>تحليلات المناقصات</h2>", unsafe_allow_html=True)
|
282 |
|
283 |
+
col1, col2 = st.columns(2)
|
|
|
|
|
|
|
284 |
|
285 |
+
with col1:
|
286 |
+
st.markdown("""
|
287 |
+
<div class="card">
|
288 |
+
<div class="card-header">
|
289 |
+
<h3 class="card-title">توزيع المناقصات حسب الجهة</h3>
|
290 |
+
</div>
|
291 |
+
</div>
|
292 |
+
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
293 |
|
294 |
+
# استخدام مخطط Streamlit
|
295 |
+
entity_data = {
|
296 |
+
"وزارة النقل": 30,
|
297 |
+
"وزارة الصحة": 25,
|
298 |
+
"وزارة التعليم": 20,
|
299 |
+
"وزارة الإسكان": 15,
|
300 |
+
"أخرى": 10
|
301 |
+
}
|
302 |
+
st.pie(entity_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
303 |
|
304 |
with col2:
|
305 |
+
st.markdown("""
|
306 |
+
<div class="card">
|
307 |
+
<div class="card-header">
|
308 |
+
<h3 class="card-title">توزيع المناقصات حسب الحالة</h3>
|
309 |
+
</div>
|
310 |
+
</div>
|
311 |
+
""", unsafe_allow_html=True)
|
312 |
+
|
313 |
+
# استخدام مخطط Streamlit
|
314 |
+
status_data = {
|
315 |
+
"مكتملة": 45,
|
316 |
+
"قيد التحليل": 30,
|
317 |
+
"جديدة": 20,
|
318 |
+
"ملغاة": 5
|
319 |
+
}
|
320 |
+
st.pie(status_data)
|
|
|
|
|
|
|
321 |
|
322 |
+
# الابتكارات والميزات
|
323 |
+
st.markdown("<h2 class='module-title'>الابتكارات والميزات</h2>", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
324 |
|
325 |
+
col1, col2, col3 = st.columns(3)
|
|
|
326 |
|
327 |
with col1:
|
328 |
st.markdown("""
|
329 |
+
<div class="innovation-card">
|
330 |
+
<div class="innovation-icon"><i class="fas fa-robot"></i></div>
|
331 |
+
<h3>المساعد الذكي</h3>
|
332 |
+
<p>استخدم الذكاء الاصطناعي لتحليل المناقصات واستخراج المعلومات المهمة بدقة عالية.</p>
|
333 |
</div>
|
334 |
""", unsafe_allow_html=True)
|
335 |
|
336 |
with col2:
|
337 |
st.markdown("""
|
338 |
+
<div class="innovation-card">
|
339 |
+
<div class="innovation-icon"><i class="fas fa-file-import"></i></div>
|
340 |
+
<h3>استخراج المستندات</h3>
|
341 |
+
<p>استخرج البيانات من مستندات المناقصات بتنسيقات مختلفة (PDF، Word، Excel) بشكل آلي.</p>
|
342 |
</div>
|
343 |
""", unsafe_allow_html=True)
|
344 |
|
345 |
with col3:
|
346 |
st.markdown("""
|
347 |
+
<div class="innovation-card">
|
348 |
+
<div class="innovation-icon"><i class="fas fa-chart-line"></i></div>
|
349 |
+
<h3>تحليلات متقدمة</h3>
|
350 |
+
<p>احصل على تحليلات متقدمة ورؤى قيمة حول المناقصات باستخدام تقنيات تحليل البيانات.</p>
|
351 |
</div>
|
352 |
""", unsafe_allow_html=True)
|
353 |
|
354 |
+
# تذييل الصفحة
|
355 |
+
st.markdown("""
|
356 |
+
<footer class="footer">
|
357 |
+
<p>جميع الحقوق محفوظة © 2025 نظام تحليل المناقصات</p>
|
358 |
+
</footer>
|
359 |
+
""", unsafe_allow_html=True)
|
360 |
+
|
361 |
+
# دالة عرض صفحة المناقصات
|
362 |
+
def show_tenders():
|
363 |
+
# ترويسة الصفحة
|
364 |
+
st.markdown("<h1 class='main-title'>المناقصات</h1>", unsafe_allow_html=True)
|
365 |
|
366 |
+
# أزرار الإجراءات
|
367 |
+
col1, col2, col3 = st.columns([1, 1, 2])
|
368 |
|
369 |
with col1:
|
370 |
+
st.button("إضافة مناقصة جديدة", type="primary")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
371 |
|
372 |
with col2:
|
373 |
+
st.button("استيراد من ملف", type="secondary")
|
374 |
+
|
375 |
+
with col3:
|
376 |
+
st.text_input("بحث في المناقصات", placeholder="ادخل كلمات البحث هنا...")
|
377 |
+
|
378 |
+
# تبويبات المناقصات
|
379 |
+
tabs = st.tabs(["جميع المناقصات", "المناقصات الجديدة", "قيد التحليل", "مكتملة", "ملغاة"])
|
380 |
+
|
381 |
+
with tabs[0]:
|
382 |
+
# بيانات المناقصات
|
383 |
+
data = {
|
384 |
+
"رقم": [1, 2, 3, 4, 5, 6, 7, 8],
|
385 |
+
"اسم المناقصة": [
|
386 |
+
"مشروع تطوير البنية التحتية",
|
387 |
+
"توريد معدات طبية",
|
388 |
+
"بناء مدرسة ثانوية",
|
389 |
+
"صيانة طرق",
|
390 |
+
"تطوير نظام إلكتروني",
|
391 |
+
"توريد أجهزة حاسب",
|
392 |
+
"إنشاء مركز بيانات",
|
393 |
+
"تطوير شبكة اتصالات"
|
394 |
+
],
|
395 |
+
"الجهة": [
|
396 |
+
"وزارة النقل",
|
397 |
+
"وزارة الصحة",
|
398 |
+
"وزارة التعليم",
|
399 |
+
"وزارة النقل",
|
400 |
+
"وزارة الداخلية",
|
401 |
+
"وزارة التعليم",
|
402 |
+
"وزارة الاتصالات",
|
403 |
+
"وزارة الاتصالات"
|
404 |
+
],
|
405 |
+
"التاريخ": [
|
406 |
+
"2025/03/15",
|
407 |
+
"2025/03/20",
|
408 |
+
"2025/03/25",
|
409 |
+
"2025/03/28",
|
410 |
+
"2025/04/01",
|
411 |
+
"2025/04/05",
|
412 |
+
"2025/04/10",
|
413 |
+
"2025/04/15"
|
414 |
+
],
|
415 |
+
"القيمة (مليون ريال)": [
|
416 |
+
15.5,
|
417 |
+
8.2,
|
418 |
+
12.0,
|
419 |
+
5.7,
|
420 |
+
3.2,
|
421 |
+
2.5,
|
422 |
+
20.0,
|
423 |
+
18.5
|
424 |
+
],
|
425 |
+
"الحالة": [
|
426 |
+
"مكتملة",
|
427 |
+
"قيد التحليل",
|
428 |
+
"جديدة",
|
429 |
+
"مكتملة",
|
430 |
+
"جديدة",
|
431 |
+
"قيد التحليل",
|
432 |
+
"جديدة",
|
433 |
+
"ملغاة"
|
434 |
+
]
|
435 |
+
}
|
436 |
|
437 |
+
# عرض الجدول
|
438 |
+
st.dataframe(data)
|
439 |
+
|
440 |
+
with tabs[1]:
|
441 |
+
# تصفية البيانات للمناقصات الجديدة فقط
|
442 |
+
new_tenders = {
|
443 |
+
"رقم": [],
|
444 |
+
"اسم المناقصة": [],
|
445 |
+
"الجهة": [],
|
446 |
+
"التاريخ": [],
|
447 |
+
"القيمة (مليون ريال)": [],
|
448 |
+
"الحالة": []
|
449 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
450 |
|
451 |
+
for i, status in enumerate(data["الحالة"]):
|
452 |
+
if status == "جديدة":
|
453 |
+
new_tenders["رقم"].append(data["رقم"][i])
|
454 |
+
new_tenders["اسم المناقصة"].append(data["اسم المناقصة"][i])
|
455 |
+
new_tenders["الجهة"].append(data["الجهة"][i])
|
456 |
+
new_tenders["التاريخ"].append(data["التاريخ"][i])
|
457 |
+
new_tenders["القيمة (مليون ريال)"].append(data["القيمة (مليون ريال)"][i])
|
458 |
+
new_tenders["الحالة"].append(data["الحالة"][i])
|
459 |
|
460 |
+
# عرض الجدول
|
461 |
+
st.dataframe(new_tenders)
|
462 |
+
|
463 |
+
with tabs[2]:
|
464 |
+
# تصفية البيانات للمناقصات قيد التحليل فقط
|
465 |
+
in_progress_tenders = {
|
466 |
+
"رقم": [],
|
467 |
+
"اسم المناقصة": [],
|
468 |
+
"الجهة": [],
|
469 |
+
"التاريخ": [],
|
470 |
+
"القيمة (مليون ريال)": [],
|
471 |
+
"الحالة": []
|
472 |
+
}
|
473 |
|
474 |
+
for i, status in enumerate(data["الحالة"]):
|
475 |
+
if status == "قيد التحليل":
|
476 |
+
in_progress_tenders["رقم"].append(data["رقم"][i])
|
477 |
+
in_progress_tenders["اسم المناقصة"].append(data["اسم المناقصة"][i])
|
478 |
+
in_progress_tenders["الجهة"].append(data["الجهة"][i])
|
479 |
+
in_progress_tenders["التاريخ"].append(data["التاريخ"][i])
|
480 |
+
in_progress_tenders["القيمة (مليون ريال)"].append(data["القيمة (مليون ريال)"][i])
|
481 |
+
in_progress_tenders["الحالة"].append(data["الحالة"][i])
|
482 |
|
483 |
+
# عرض الجدول
|
484 |
+
st.dataframe(in_progress_tenders)
|
|
|
|
|
|
|
485 |
|
486 |
+
with tabs[3]:
|
487 |
+
# تصفية البيانات للمناقصات المكتملة فقط
|
488 |
+
completed_tenders = {
|
489 |
+
"رقم": [],
|
490 |
+
"اسم المناقصة": [],
|
491 |
+
"الجهة": [],
|
492 |
+
"التاريخ": [],
|
493 |
+
"القيمة (مليون ريال)": [],
|
494 |
+
"الحالة": []
|
495 |
+
}
|
496 |
+
|
497 |
+
for i, status in enumerate(data["الحالة"]):
|
498 |
+
if status == "مكتملة":
|
499 |
+
completed_tenders["رقم"].append(data["رقم"][i])
|
500 |
+
completed_tenders["اسم المناقصة"].append(data["اسم المناقصة"][i])
|
501 |
+
completed_tenders["الجهة"].append(data["الجهة"][i])
|
502 |
+
completed_tenders["التاريخ"].append(data["التاريخ"][i])
|
503 |
+
completed_tenders["القيمة (مليون ريال)"].append(data["القيمة (مليون ريال)"][i])
|
504 |
+
completed_tenders["الحالة"].append(data["الحالة"][i])
|
505 |
+
|
506 |
+
# عرض الجدول
|
507 |
+
st.dataframe(completed_tenders)
|
508 |
|
509 |
+
with tabs[4]:
|
510 |
+
# تصفية البيانات للمناقصات الملغاة فقط
|
511 |
+
cancelled_tenders = {
|
512 |
+
"رقم": [],
|
513 |
+
"اسم المناقصة": [],
|
514 |
+
"الجهة": [],
|
515 |
+
"التاريخ": [],
|
516 |
+
"القيمة (مليون ريال)": [],
|
517 |
+
"الحالة": []
|
518 |
+
}
|
519 |
+
|
520 |
+
for i, status in enumerate(data["الحالة"]):
|
521 |
+
if status == "ملغاة":
|
522 |
+
cancelled_tenders["رقم"].append(data["رقم"][i])
|
523 |
+
cancelled_tenders["اسم المناقصة"].append(data["اسم المناقصة"][i])
|
524 |
+
cancelled_tenders["الجهة"].append(data["الجهة"][i])
|
525 |
+
cancelled_tenders["التاريخ"].append(data["التاريخ"][i])
|
526 |
+
cancelled_tenders["القيمة (مليون ريال)"].append(data["القيمة (مليون ريال)"][i])
|
527 |
+
cancelled_tenders["الحالة"].append(data["الحالة"][i])
|
528 |
+
|
529 |
+
# عرض الجدول
|
530 |
+
st.dataframe(cancelled_tenders)
|
531 |
|
532 |
+
# تذييل الصفحة
|
533 |
st.markdown("""
|
534 |
+
<footer class="footer">
|
535 |
+
<p>جميع الحقوق محفوظة © 2025 نظام تحليل المناقصات</p>
|
536 |
+
</footer>
|
|
|
537 |
""", unsafe_allow_html=True)
|
538 |
|
539 |
+
# دالة عرض صفحة المساعد الذكي
|
540 |
+
def show_ai_assistant():
|
541 |
+
# ترويسة الصفحة
|
542 |
+
st.markdown("<h1 class='main-title'>المساعد الذكي</h1>", unsafe_allow_html=True)
|
543 |
|
544 |
+
# وصف المساعد الذكي
|
545 |
st.markdown("""
|
546 |
+
<div class="card mb-4">
|
547 |
+
<div class="card-body">
|
548 |
+
<p>المساعد الذكي يساعدك في تحليل المناقصات واستخراج المعلومات المهمة منها باستخدام تقنيات الذكاء الاصطناعي المتقدمة.</p>
|
549 |
+
</div>
|
550 |
</div>
|
551 |
""", unsafe_allow_html=True)
|
552 |
|
553 |
+
# واجهة المساعد الذكي
|
554 |
+
st.markdown("<h2 class='module-title'>تحليل المناقصة</h2>", unsafe_allow_html=True)
|
555 |
|
556 |
+
# خيارات التحليل
|
557 |
+
col1, col2 = st.columns(2)
|
558 |
|
559 |
with col1:
|
560 |
+
analysis_type = st.selectbox(
|
561 |
+
"نوع التحليل",
|
562 |
+
["تحليل شامل", "استخراج الشروط", "تحليل الأسعار", "تقييم المخاطر"]
|
563 |
+
)
|
|
|
|
|
|
|
|
|
|
|
564 |
|
565 |
with col2:
|
566 |
+
model = st.selectbox(
|
567 |
+
"نموذج الذكاء الاصطناعي",
|
568 |
+
["GPT-4o", "Claude 3 Opus", "Claude 3 Sonnet"]
|
569 |
+
)
|
|
|
|
|
|
|
|
|
|
|
570 |
|
571 |
+
# تحميل ملف المناقصة
|
572 |
+
uploaded_file = st.file_uploader("تحميل ملف المناقصة", type=["pdf", "docx", "txt"])
|
573 |
+
|
574 |
+
# أو إدخال نص المناقصة
|
575 |
+
st.markdown("<p>أو</p>", unsafe_allow_html=True)
|
576 |
+
tender_text = st.text_area("نص المناقصة", height=200)
|
577 |
+
|
578 |
+
# زر التحليل
|
579 |
+
if st.button("تحليل المناقصة", type="primary"):
|
580 |
+
if uploaded_file is not None or tender_text:
|
581 |
+
# عرض شريط التقدم
|
582 |
+
progress_bar = st.progress(0)
|
583 |
+
for i in range(100):
|
584 |
+
# تحديث شريط التقدم
|
585 |
+
progress_bar.progress(i + 1)
|
586 |
+
if i < 30:
|
587 |
+
st.caption("جاري قراءة المناقصة...")
|
588 |
+
elif i < 60:
|
589 |
+
st.caption("جاري تحليل المحتوى...")
|
590 |
+
elif i < 90:
|
591 |
+
st.caption("جاري إعداد التقرير...")
|
592 |
+
else:
|
593 |
+
st.caption("اكتمل التحليل!")
|
594 |
+
|
595 |
+
# عرض نتائج التحليل
|
596 |
+
st.success("تم تحليل المناقصة بنجاح!")
|
597 |
+
|
598 |
+
st.markdown("""
|
599 |
+
<div class="card">
|
600 |
+
<div class="card-header">
|
601 |
+
<h3 class="card-title">نتائج التحليل</h3>
|
602 |
+
</div>
|
603 |
+
<div class="card-body">
|
604 |
+
<h4>ملخص المناقصة</h4>
|
605 |
+
<p>هذه مناقصة لمشروع تطوير البنية التحتية في مدينة الرياض. تشمل المناقصة أعمال إنشاء طرق وجسور وأنفاق.</p>
|
606 |
+
|
607 |
+
<h4>الشروط الرئيسية</h4>
|
608 |
+
<ul>
|
609 |
+
<li>مدة التنفيذ: 24 شهراً</li>
|
610 |
+
<li>قيمة الضمان: 5% من قيمة العقد</li>
|
611 |
+
<li>خبرة سابقة: 10 سنوات في مشاريع مماثلة</li>
|
612 |
+
<li>عدد المهندسين المطلوبين: 15 مهندساً</li>
|
613 |
+
</ul>
|
614 |
+
|
615 |
+
<h4>المخاطر المحتملة</h4>
|
616 |
+
<ul>
|
617 |
+
<li>مخاطر عالية: تأخر التسليم بسبب ضيق الجدول الزمني</li>
|
618 |
+
<li>مخاطر متوسطة: ارتفاع تكاليف المواد</li>
|
619 |
+
<li>مخاطر منخفضة: مشاكل في التصاريح</li>
|
620 |
+
</ul>
|
621 |
+
</div>
|
622 |
+
</div>
|
623 |
+
""", unsafe_allow_html=True)
|
624 |
+
else:
|
625 |
+
st.error("يرجى تحميل ملف المناقصة أو إدخال نص المناقصة")
|
626 |
|
627 |
+
# تذييل الصفحة
|
628 |
+
st.markdown("""
|
629 |
+
<footer class="footer">
|
630 |
+
<p>جميع الحقوق محفوظة © 2025 نظام تحليل المناقصات</p>
|
631 |
+
</footer>
|
632 |
+
""", unsafe_allow_html=True)
|
633 |
+
|
634 |
+
# دالة عرض صفحة استخراج المستندات
|
635 |
+
def show_document_extraction():
|
636 |
+
# ترويسة الصفحة
|
637 |
+
st.markdown("<h1 class='main-title'>استخراج المستندات</h1>", unsafe_allow_html=True)
|
638 |
|
639 |
+
# وصف استخراج المستندات
|
640 |
st.markdown("""
|
641 |
+
<div class="card mb-4">
|
642 |
+
<div class="card-body">
|
643 |
+
<p>استخرج البيانات من مستندات المناقصات بتنسيقات مختلفة (PDF، Word، Excel) بشكل آلي.</p>
|
644 |
+
</div>
|
645 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
646 |
""", unsafe_allow_html=True)
|
647 |
|
648 |
+
# واجهة استخراج المستندات
|
649 |
+
st.markdown("<h2 class='module-title'>استخراج البيانات</h2>", unsafe_allow_html=True)
|
650 |
|
651 |
+
# خيارات الاستخراج
|
652 |
col1, col2 = st.columns(2)
|
653 |
|
654 |
with col1:
|
655 |
+
extraction_type = st.selectbox(
|
656 |
+
"نوع الاستخراج",
|
657 |
+
["استخراج جميع البيانات", "استخراج الجداول فقط", "استخراج النصوص فقط"]
|
658 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
659 |
|
660 |
with col2:
|
661 |
+
output_format = st.selectbox(
|
662 |
+
"تنسيق الإخراج",
|
663 |
+
["Excel", "CSV", "JSON", "Text"]
|
664 |
+
)
|
665 |
+
|
666 |
+
# تحميل ملف المستند
|
667 |
+
uploaded_files = st.file_uploader("تحميل المستندات", type=["pdf", "docx", "xlsx", "xls"], accept_multiple_files=True)
|
668 |
+
|
669 |
+
# زر الاستخراج
|
670 |
+
if st.button("استخراج البيانات", type="primary"):
|
671 |
+
if uploaded_files:
|
672 |
+
# عرض شريط التقدم
|
673 |
+
progress_bar = st.progress(0)
|
674 |
+
for i in range(100):
|
675 |
+
# تحديث شريط التقدم
|
676 |
+
progress_bar.progress(i + 1)
|
677 |
+
if i < 40:
|
678 |
+
st.caption("جاري قراءة المستندات...")
|
679 |
+
elif i < 80:
|
680 |
+
st.caption("جاري استخراج البيانات...")
|
681 |
+
else:
|
682 |
+
st.caption("جاري إعداد النتائج...")
|
683 |
|
684 |
+
# عرض نتائج الاستخراج
|
685 |
+
st.success(f"تم استخراج البيانات من {len(uploaded_files)} مستند بنجاح!")
|
|
|
|
|
|
|
|
|
|
|
|
|
686 |
|
687 |
+
# عرض البيانات المستخرجة
|
688 |
+
st.markdown("<h3>البيانات المستخرجة</h3>", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
689 |
|
690 |
+
# بيانات عينة
|
691 |
+
data = {
|
692 |
+
"رقم المناقصة": ["T-2025-001", "T-2025-002", "T-2025-003"],
|
693 |
+
"اسم المناقصة": [
|
694 |
+
"مشروع تطوير البنية التحتية",
|
695 |
+
"توريد معدات طبية",
|
696 |
+
"بناء مدرسة ثانوية"
|
697 |
+
],
|
698 |
+
"الجهة": [
|
699 |
+
"وزارة النقل",
|
700 |
+
"وزارة الصحة",
|
701 |
+
"وزارة التعليم"
|
702 |
+
],
|
703 |
+
"تاريخ الإعلان": [
|
704 |
+
"2025/03/01",
|
705 |
+
"2025/03/05",
|
706 |
+
"2025/03/10"
|
707 |
+
],
|
708 |
+
"تاريخ الإغلاق": [
|
709 |
+
"2025/04/01",
|
710 |
+
"2025/04/05",
|
711 |
+
"2025/04/10"
|
712 |
+
],
|
713 |
+
"القيمة التقديرية": [
|
714 |
+
"15,000,000 ريال",
|
715 |
+
"8,200,000 ريال",
|
716 |
+
"12,000,000 ريال"
|
717 |
+
]
|
718 |
+
}
|
719 |
+
|
720 |
+
st.dataframe(data)
|
721 |
+
|
722 |
+
# زر تنزيل النتائج
|
723 |
+
st.download_button(
|
724 |
+
label="تنزيل النتائج",
|
725 |
+
data="بيانات عينة للتنزيل",
|
726 |
+
file_name=f"extracted_data.{output_format.lower()}",
|
727 |
+
mime="text/plain"
|
728 |
+
)
|
729 |
+
else:
|
730 |
+
st.error("يرجى تحميل مستند واحد على الأقل")
|
731 |
+
|
732 |
+
# تذييل الصفحة
|
733 |
+
st.markdown("""
|
734 |
+
<footer class="footer">
|
735 |
+
<p>جميع الحقوق محفوظة © 2025 نظام تحليل المناقصات</p>
|
736 |
+
</footer>
|
737 |
+
""", unsafe_allow_html=True)
|
738 |
|
739 |
+
# دالة عرض صفحة التحليلات
|
740 |
+
def show_analysis():
|
741 |
+
# ترويسة الصفحة
|
742 |
+
st.markdown("<h1 class='main-title'>التحليلات</h1>", unsafe_allow_html=True)
|
743 |
|
744 |
+
# وصف التحليلات
|
745 |
st.markdown("""
|
746 |
+
<div class="card mb-4">
|
747 |
+
<div class="card-body">
|
748 |
+
<p>احصل على تحليلات متقدمة ورؤى قيمة حول المناقصات باستخدام تقنيات تحليل البيانات.</p>
|
749 |
+
</div>
|
750 |
</div>
|
751 |
""", unsafe_allow_html=True)
|
752 |
|
753 |
+
# فلاتر التحليلات
|
754 |
+
st.markdown("<h2 class='module-title'>فلاتر التحليلات</h2>", unsafe_allow_html=True)
|
755 |
|
756 |
col1, col2, col3 = st.columns(3)
|
757 |
|
758 |
with col1:
|
759 |
+
period = st.selectbox(
|
760 |
+
"الفترة الزمنية",
|
761 |
+
["الربع الحالي", "السنة الحالية", "العام الماضي", "آخر 3 سنوات", "الكل"]
|
762 |
+
)
|
|
|
|
|
|
|
|
|
|
|
763 |
|
764 |
with col2:
|
765 |
+
entity = st.multiselect(
|
766 |
+
"الجهة",
|
767 |
+
["وزارة النقل", "وزارة الصحة", "وزارة التعليم", "وزارة الإسكان", "وزارة الداخلية", "وزارة الاتصالات"],
|
768 |
+
default=["وزارة النقل", "وزارة الصحة", "وزارة التعليم"]
|
769 |
+
)
|
|
|
|
|
|
|
|
|
770 |
|
771 |
with col3:
|
772 |
+
status = st.multiselect(
|
773 |
+
"الحالة",
|
774 |
+
["مكتملة", "قيد التحليل", "جديدة", "ملغاة"],
|
775 |
+
default=["مكتملة", "قيد التحليل", "جديدة"]
|
776 |
+
)
|
|
|
|
|
|
|
|
|
777 |
|
778 |
+
# زر تطبيق الفلاتر
|
779 |
+
st.button("تطبيق الفلاتر", type="primary")
|
780 |
|
781 |
+
# عرض التحليلات
|
782 |
+
st.markdown("<h2 class='module-title'>تحليلات المناقصات</h2>", unsafe_allow_html=True)
|
783 |
|
784 |
+
# تحليلات الاتجاهات
|
785 |
+
st.markdown("<h3>اتجاهات المناقصات</h3>", unsafe_allow_html=True)
|
786 |
+
|
787 |
+
# بيانات عينة للرسم البياني
|
788 |
+
chart_data = {
|
789 |
+
"الشهر": ["يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو"],
|
790 |
+
"عدد المناقصات": [12, 19, 15, 21, 25, 18]
|
791 |
+
}
|
792 |
+
|
793 |
+
# عرض الرسم البياني
|
794 |
+
st.line_chart(chart_data, x="الشهر", y="عدد المناقصات")
|
795 |
+
|
796 |
+
# توزيع المناقصات حسب الجهة
|
797 |
+
st.markdown("<h3>توزيع المناقصات حسب الجهة</h3>", unsafe_allow_html=True)
|
798 |
+
|
799 |
+
# بيانات عينة للرسم البياني
|
800 |
+
entity_data = {
|
801 |
+
"وزارة النقل": 30,
|
802 |
+
"وزارة الصحة": 25,
|
803 |
+
"وزارة التعليم": 20,
|
804 |
+
"وزارة الإسكان": 15,
|
805 |
+
"وزارة الداخلية": 7,
|
806 |
+
"وزارة الاتصالات": 3
|
807 |
+
}
|
808 |
+
|
809 |
+
# عرض الرسم البياني
|
810 |
+
st.bar_chart(entity_data)
|
811 |
+
|
812 |
+
# توزيع المناقصات حسب القيمة
|
813 |
+
st.markdown("<h3>توزيع المناقصات حسب القيمة</h3>", unsafe_allow_html=True)
|
814 |
+
|
815 |
+
# بيانات عينة للرسم البياني
|
816 |
+
value_data = {
|
817 |
+
"أقل من 1 مليون": 15,
|
818 |
+
"1-5 مليون": 30,
|
819 |
+
"5-10 مليون": 25,
|
820 |
+
"10-50 مليون": 20,
|
821 |
+
"أكثر من 50 مليون": 10
|
822 |
+
}
|
823 |
+
|
824 |
+
# عرض الرسم البياني
|
825 |
+
st.bar_chart(value_data)
|
826 |
+
|
827 |
+
# تذيي�� الصفحة
|
828 |
+
st.markdown("""
|
829 |
+
<footer class="footer">
|
830 |
+
<p>جميع الحقوق محفوظة © 2025 نظام تحليل المناقصات</p>
|
831 |
+
</footer>
|
832 |
+
""", unsafe_allow_html=True)
|
833 |
+
|
834 |
+
# دالة عرض صفحة التقارير
|
835 |
+
def show_reports():
|
836 |
+
# ترويسة الصفحة
|
837 |
+
st.markdown("<h1 class='main-title'>التقارير</h1>", unsafe_allow_html=True)
|
838 |
+
|
839 |
+
# وصف التقارير
|
840 |
+
st.markdown("""
|
841 |
+
<div class="card mb-4">
|
842 |
+
<div class="card-body">
|
843 |
+
<p>إنشاء وعرض تقارير مفصلة عن المناقصات والتحليلات.</p>
|
844 |
</div>
|
845 |
+
</div>
|
846 |
+
""", unsafe_allow_html=True)
|
847 |
|
848 |
+
# أنواع التقارير
|
849 |
+
st.markdown("<h2 class='module-title'>أنواع التقارير</h2>", unsafe_allow_html=True)
|
850 |
+
|
851 |
+
# تبويبات التقارير
|
852 |
+
tabs = st.tabs(["التقارير الدورية", "تقارير الأداء", "تقارير المقارنة", "تقارير مخصصة"])
|
853 |
+
|
854 |
+
with tabs[0]:
|
855 |
+
st.markdown("<h3>التقارير الدورية</h3>", unsafe_allow_html=True)
|
856 |
+
|
857 |
+
col1, col2 = st.columns(2)
|
858 |
+
|
859 |
+
with col1:
|
860 |
+
report_period = st.selectbox(
|
861 |
+
"الفترة",
|
862 |
+
["يومي", "أسبوعي", "شهري", "ربع سنوي", "سنوي"]
|
863 |
+
)
|
864 |
+
|
865 |
+
with col2:
|
866 |
+
report_type = st.selectbox(
|
867 |
+
"نوع التقرير",
|
868 |
+
["تقرير ملخص", "تقرير مفصل", "تقرير إحصائي"]
|
869 |
+
)
|
870 |
+
|
871 |
+
# زر إنشاء التقرير
|
872 |
+
if st.button("إنشاء التقرير", type="primary"):
|
873 |
+
# عرض شريط التقدم
|
874 |
+
progress_bar = st.progress(0)
|
875 |
+
for i in range(100):
|
876 |
+
# تحديث شريط التقدم
|
877 |
+
progress_bar.progress(i + 1)
|
878 |
+
if i < 50:
|
879 |
+
st.caption("جاري إعداد التقرير...")
|
880 |
+
else:
|
881 |
+
st.caption("جاري تنسيق التقرير...")
|
882 |
|
883 |
+
# عرض التقرير
|
884 |
+
st.success("تم إنشاء التقرير بنجاح!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
885 |
|
886 |
+
st.markdown("""
|
887 |
+
<div class="card">
|
888 |
+
<div class="card-header">
|
889 |
+
<h3 class="card-title">تقرير شهري - مارس 2025</h3>
|
890 |
</div>
|
891 |
+
<div class="card-body">
|
892 |
+
<h4>ملخص التقرير</h4>
|
893 |
+
<p>هذا تقرير شهري يوضح أداء المناقصات خلال شهر مارس 2025.</p>
|
894 |
+
|
895 |
+
<h4>إحصائيات المناقصات</h4>
|
896 |
+
<ul>
|
897 |
+
<li>إجمالي المناقصات: 45</li>
|
898 |
+
<li>المناقصات الجديدة: 15</li>
|
899 |
+
<li>المناقصات المكتملة: 20</li>
|
900 |
+
<li>المناقصات قيد التحليل: 8</li>
|
901 |
+
<li>المناقصات الملغاة: 2</li>
|
902 |
+
</ul>
|
903 |
+
|
904 |
+
<h4>توزيع المناقصات حسب الجهة</h4>
|
905 |
+
<ul>
|
906 |
+
<li>وزارة النقل: 12</li>
|
907 |
+
<li>وزارة الصحة: 10</li>
|
908 |
+
<li>وزارة التعليم: 8</li>
|
909 |
+
<li>وزارة الإسكان: 7</li>
|
910 |
+
<li>أخرى: 8</li>
|
911 |
+
</ul>
|
912 |
</div>
|
913 |
</div>
|
914 |
+
""", unsafe_allow_html=True)
|
915 |
|
916 |
+
# زر تنزيل التقرير
|
917 |
+
st.download_button(
|
918 |
+
label="تنزيل التقرير (PDF)",
|
919 |
+
data="بيانات عينة للتنزيل",
|
920 |
+
file_name="monthly_report_march_2025.pdf",
|
921 |
+
mime="application/pdf"
|
922 |
+
)
|
923 |
+
|
924 |
+
with tabs[1]:
|
925 |
+
st.markdown("<h3>تقارير الأداء</h3>", unsafe_allow_html=True)
|
926 |
+
|
927 |
+
# محتوى تقارير الأداء
|
928 |
+
st.markdown("""
|
929 |
+
<p>تقارير الأداء توفر معلومات عن أداء المناقصات والمشاريع.</p>
|
930 |
+
""", unsafe_allow_html=True)
|
931 |
+
|
932 |
+
# أمثلة على تقارير الأداء
|
933 |
+
st.markdown("""
|
934 |
+
<div class="card mb-3">
|
935 |
+
<div class="card-header">
|
936 |
+
<h4 class="card-title">تقرير أداء المناقصات - الربع الأول 2025</h4>
|
937 |
+
</div>
|
938 |
+
<div class="card-body">
|
939 |
+
<p>تقرير يوضح أداء المناقصات خلال الربع الأول من عام 2025.</p>
|
940 |
+
<button class="btn btn-primary btn-sm">عرض التقرير</button>
|
941 |
</div>
|
942 |
</div>
|
943 |
+
|
944 |
+
<div class="card mb-3">
|
945 |
+
<div class="card-header">
|
946 |
+
<h4 class="card-title">تقرير أداء المشاريع - 2024</h4>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
947 |
</div>
|
948 |
+
<div class="card-body">
|
949 |
+
<p>تقرير يوضح أداء المشاريع خلال عام 2024.</p>
|
950 |
+
<button class="btn btn-primary btn-sm">عرض التقرير</button>
|
951 |
</div>
|
952 |
</div>
|
953 |
+
""", unsafe_allow_html=True)
|
954 |
|
955 |
+
with tabs[2]:
|
956 |
+
st.markdown("<h3>تقارير المقارنة</h3>", unsafe_allow_html=True)
|
957 |
+
|
958 |
+
# محتوى تقارير المقارنة
|
959 |
+
st.markdown("""
|
960 |
+
<p>تقارير المقارنة توفر مقارنة بين المناقصات والمشاريع المختلفة.</p>
|
961 |
+
""", unsafe_allow_html=True)
|
962 |
+
|
963 |
+
# أمثلة على تقارير المقارنة
|
964 |
+
st.markdown("""
|
965 |
+
<div class="card mb-3">
|
966 |
+
<div class="card-header">
|
967 |
+
<h4 class="card-title">تقرير مقارنة المناقصات حسب الجهة</h4>
|
968 |
</div>
|
969 |
+
<div class="card-body">
|
970 |
+
<p>تقرير يقارن بين المناقصات المختلفة حسب الجهة.</p>
|
971 |
+
<button class="btn btn-primary btn-sm">عرض التقرير</button>
|
972 |
</div>
|
973 |
</div>
|
974 |
+
|
975 |
+
<div class="card mb-3">
|
976 |
+
<div class="card-header">
|
977 |
+
<h4 class="card-title">تقرير مقارنة المناقصات حسب القيمة</h4>
|
|
|
|
|
|
|
|
|
|
|
978 |
</div>
|
979 |
+
<div class="card-body">
|
980 |
+
<p>تقرير يقارن بين المناقصات المختلفة حسب القيمة.</p>
|
981 |
+
<button class="btn btn-primary btn-sm">عرض التقرير</button>
|
982 |
</div>
|
983 |
</div>
|
984 |
+
""", unsafe_allow_html=True)
|
985 |
+
|
986 |
+
with tabs[3]:
|
987 |
+
st.markdown("<h3>تقارير مخصصة</h3>", unsafe_allow_html=True)
|
988 |
+
|
989 |
+
# محتوى التقارير المخصصة
|
990 |
+
st.markdown("""
|
991 |
+
<p>إنشاء تقارير مخصصة حسب احتياجاتك.</p>
|
992 |
+
""", unsafe_allow_html=True)
|
993 |
+
|
994 |
+
# نموذج إنشاء تقرير مخصص
|
995 |
+
st.markdown("<h4>إنشاء تقرير مخصص</h4>", unsafe_allow_html=True)
|
996 |
+
|
997 |
+
col1, col2 = st.columns(2)
|
998 |
+
|
999 |
+
with col1:
|
1000 |
+
custom_report_name = st.text_input("اسم التقرير", "تقرير مخصص")
|
1001 |
+
custom_report_period = st.selectbox(
|
1002 |
+
"الفترة الزمنية",
|
1003 |
+
["الربع الحالي", "السنة الحالية", "العام الماضي", "آخر 3 سنوات", "فترة مخصصة"]
|
1004 |
+
)
|
1005 |
+
|
1006 |
+
if custom_report_period == "فترة مخصصة":
|
1007 |
+
start_date = st.date_input("تاريخ البداية")
|
1008 |
+
end_date = st.date_input("تاريخ النهاية")
|
1009 |
+
|
1010 |
+
with col2:
|
1011 |
+
custom_report_entities = st.multiselect(
|
1012 |
+
"الجهات",
|
1013 |
+
["وزارة النقل", "وزارة الصحة", "وزارة التعليم", "وزارة الإسكان", "وزارة الداخلية", "وزارة الاتصالات"],
|
1014 |
+
default=["وزارة النقل", "وزارة الصحة", "وزارة التعليم"]
|
1015 |
+
)
|
1016 |
+
|
1017 |
+
custom_report_sections = st.multiselect(
|
1018 |
+
"أقسام التقرير",
|
1019 |
+
["ملخص تنفيذي", "إحصائيات المناقصات", "توزيع المناقصات حسب الجهة", "توزيع المناقصات حسب القيمة", "توزيع المناقصات حسب الحالة", "رسوم بيانية", "جداول تفصيلية"],
|
1020 |
+
default=["ملخص تنفيذي", "إحصائيات المناقصات", "رسوم بيانية"]
|
1021 |
+
)
|
1022 |
+
|
1023 |
+
# زر إنشاء التقرير المخصص
|
1024 |
+
if st.button("إنشاء التقرير المخصص", type="primary"):
|
1025 |
+
st.success("تم إنشاء التقرير المخصص بنجاح!")
|
1026 |
+
|
1027 |
+
# زر تنزيل التقرير المخصص
|
1028 |
+
st.download_button(
|
1029 |
+
label="تنزيل التقرير المخصص (PDF)",
|
1030 |
+
data="بيانات عينة للتنزيل",
|
1031 |
+
file_name="custom_report.pdf",
|
1032 |
+
mime="application/pdf"
|
1033 |
+
)
|
1034 |
+
|
1035 |
+
# تذييل الصفحة
|
1036 |
+
st.markdown("""
|
1037 |
+
<footer class="footer">
|
1038 |
+
<p>جميع الحقوق محفوظة © 2025 نظام تحليل المناقصات</p>
|
1039 |
+
</footer>
|
1040 |
""", unsafe_allow_html=True)
|
1041 |
|
1042 |
+
# دالة عرض صفحة الإعدادات
|
1043 |
+
def show_settings():
|
1044 |
+
# ترويسة الصفحة
|
1045 |
+
st.markdown("<h1 class='main-title'>الإعدادات</h1>", unsafe_allow_html=True)
|
1046 |
+
|
1047 |
+
# وصف الإعدادات
|
1048 |
+
st.markdown("""
|
1049 |
+
<div class="card mb-4">
|
1050 |
+
<div class="card-body">
|
1051 |
+
<p>تخصيص إعدادات النظام حسب احتياجاتك.</p>
|
1052 |
+
</div>
|
1053 |
+
</div>
|
1054 |
+
""", unsafe_allow_html=True)
|
1055 |
+
|
1056 |
+
# تبويبات الإعدادات
|
1057 |
+
tabs = st.tabs(["إعدادات عامة", "إعدادات المستخدم", "إعدادات الذكاء الاصطناعي", "إعدادات الواجهة"])
|
1058 |
+
|
1059 |
+
with tabs[0]:
|
1060 |
+
st.markdown("<h3>إعدادات عامة</h3>", unsafe_allow_html=True)
|
1061 |
|
1062 |
+
# إعدادات اللغة
|
1063 |
+
st.markdown("<h4>إعدادات اللغة</h4>", unsafe_allow_html=True)
|
1064 |
+
language = st.selectbox(
|
1065 |
+
"اللغة",
|
1066 |
+
["العربية", "English"],
|
1067 |
+
index=0
|
1068 |
+
)
|
1069 |
|
1070 |
+
# إعدادات المنطقة الزمنية
|
1071 |
+
st.markdown("<h4>إعدادات المنطقة الزمنية</h4>", unsafe_allow_html=True)
|
1072 |
+
timezone = st.selectbox(
|
1073 |
+
"المنطقة الزمنية",
|
1074 |
+
["توقيت الرياض (GMT+3)", "توقيت جرينتش (GMT)", "توقيت نيويورك (GMT-5)"],
|
1075 |
+
index=0
|
1076 |
+
)
|
1077 |
|
1078 |
+
# إعدادات التنبيهات
|
1079 |
+
st.markdown("<h4>إعدادات التنبيهات</h4>", unsafe_allow_html=True)
|
|
|
1080 |
|
1081 |
+
email_notifications = st.checkbox("تنبيهات البريد الإلكتروني", value=True)
|
1082 |
+
browser_notifications = st.checkbox("تنبيهات المتصفح", value=True)
|
1083 |
+
|
1084 |
+
if email_notifications:
|
1085 |
+
email = st.text_input("البريد الإلكتروني للتنبيهات")
|
1086 |
+
|
1087 |
+
with tabs[1]:
|
1088 |
+
st.markdown("<h3>إعدادات المستخدم</h3>", unsafe_allow_html=True)
|
1089 |
+
|
1090 |
+
# معلومات المستخدم
|
1091 |
+
st.markdown("<h4>معلومات المستخدم</h4>", unsafe_allow_html=True)
|
1092 |
+
|
1093 |
+
col1, col2 = st.columns(2)
|
1094 |
+
|
1095 |
+
with col1:
|
1096 |
+
username = st.text_input("اسم المستخدم", "admin")
|
1097 |
+
email = st.text_input("البريد الإلكتروني", "[email protected]")
|
1098 |
+
|
1099 |
+
with col2:
|
1100 |
+
full_name = st.text_input("الاسم الكامل", "مدير النظام")
|
1101 |
+
phone = st.text_input("رقم الهاتف", "+966 5XXXXXXXX")
|
1102 |
+
|
1103 |
+
# تغيير كلمة المرور
|
1104 |
+
st.markdown("<h4>تغيير كلمة المرور</h4>", unsafe_allow_html=True)
|
1105 |
+
|
1106 |
+
current_password = st.text_input("كلمة المرور الحالية", type="password")
|
1107 |
+
new_password = st.text_input("كلمة المرور الجديدة", type="password")
|
1108 |
+
confirm_password = st.text_input("تأكيد كلمة المرور الجديدة", type="password")
|
1109 |
+
|
1110 |
+
if st.button("تغيير كلمة المرور"):
|
1111 |
+
if not current_password or not new_password or not confirm_password:
|
1112 |
+
st.error("يرجى ملء جميع الحقول")
|
1113 |
+
elif new_password != confirm_password:
|
1114 |
+
st.error("كلمة المرور الجديدة وتأكيدها غير متطابقين")
|
1115 |
+
else:
|
1116 |
+
st.success("تم تغيير كلمة المرور بنجاح")
|
1117 |
+
|
1118 |
+
with tabs[2]:
|
1119 |
+
st.markdown("<h3>إعدادات الذكاء الاصطناعي</h3>", unsafe_allow_html=True)
|
1120 |
+
|
1121 |
+
# نماذج الذكاء الاصطناعي
|
1122 |
+
st.markdown("<h4>نماذج الذكاء الاصطناعي</h4>", unsafe_allow_html=True)
|
1123 |
+
|
1124 |
+
default_model = st.selectbox(
|
1125 |
+
"النموذج الافتراضي",
|
1126 |
+
["GPT-4o", "Claude 3 Opus", "Claude 3 Sonnet"]
|
1127 |
+
)
|
1128 |
+
|
1129 |
+
# إعدادات API
|
1130 |
+
st.markdown("<h4>إعدادات API</h4>", unsafe_allow_html=True)
|
1131 |
+
|
1132 |
+
openai_api_key = st.text_input("مفتاح API لـ OpenAI", type="password")
|
1133 |
+
anthropic_api_key = st.text_input("مفتاح API لـ Anthropic", type="password")
|
1134 |
+
|
1135 |
+
# إعدادات متقدمة
|
1136 |
+
st.markdown("<h4>إعدادات متقدمة</h4>", unsafe_allow_html=True)
|
1137 |
+
|
1138 |
+
temperature = st.slider("درجة الإبداعية (Temperature)", 0.0, 1.0, 0.7)
|
1139 |
+
max_tokens = st.slider("الحد الأقصى للرموز (Max Tokens)", 100, 4000, 2000)
|
1140 |
+
|
1141 |
+
with tabs[3]:
|
1142 |
+
st.markdown("<h3>إعدادات الواجهة</h3>", unsafe_allow_html=True)
|
1143 |
+
|
1144 |
+
# السمة
|
1145 |
+
st.markdown("<h4>السمة</h4>", unsafe_allow_html=True)
|
1146 |
+
|
1147 |
+
theme = st.selectbox(
|
1148 |
+
"السمة",
|
1149 |
+
["فاتح", "داكن", "تلقائي (حسب إعدادات النظام)"]
|
1150 |
+
)
|
1151 |
+
|
1152 |
+
# الألوان
|
1153 |
+
st.markdown("<h4>الألوان</h4>", unsafe_allow_html=True)
|
1154 |
+
|
1155 |
+
primary_color = st.color_picker("اللون الرئيسي", "#2563eb")
|
1156 |
+
secondary_color = st.color_picker("اللون الثانوي", "#10b981")
|
1157 |
+
|
1158 |
+
# حجم الخط
|
1159 |
+
st.markdown("<h4>حجم الخط</h4>", unsafe_allow_html=True)
|
1160 |
+
|
1161 |
+
font_size = st.select_slider(
|
1162 |
+
"حجم الخط",
|
1163 |
+
options=["صغير", "متوسط", "كبير", "كبير جداً"]
|
1164 |
+
)
|
1165 |
+
|
1166 |
+
# زر حفظ الإعدادات
|
1167 |
+
if st.button("حفظ الإعدادات", type="primary"):
|
1168 |
+
st.success("تم حفظ الإعدادات بنجاح")
|
1169 |
+
|
1170 |
+
# تذييل الصفحة
|
1171 |
+
st.markdown("""
|
1172 |
+
<footer class="footer">
|
1173 |
+
<p>جميع الحقوق محفوظة © 2025 نظام تحليل المناقصات</p>
|
1174 |
+
</footer>
|
1175 |
+
""", unsafe_allow_html=True)
|
1176 |
+
|
1177 |
+
# التأكد من وجود المجلدات والملفات اللازمة
|
1178 |
+
ensure_directories()
|
1179 |
+
create_default_logo()
|
1180 |
+
|
1181 |
+
# تحميل CSS
|
1182 |
+
load_css()
|
1183 |
+
|
1184 |
+
# إضافة Font Awesome
|
1185 |
+
load_font_awesome()
|
1186 |
+
|
1187 |
+
# الشريط الجانبي
|
1188 |
+
with st.sidebar:
|
1189 |
+
# شعار النظام
|
1190 |
+
logo_path = os.path.join(os.path.dirname(__file__), "static/images/logo.png")
|
1191 |
+
if os.path.exists(logo_path):
|
1192 |
+
st.image(logo_path, width=150)
|
1193 |
+
else:
|
1194 |
+
st.markdown("<h3>نظام تحليل المناقصات</h3>", unsafe_allow_html=True)
|
1195 |
+
|
1196 |
+
st.markdown("<h2 class='sidebar-title'>نظام تحليل المناقصات</h2>", unsafe_allow_html=True)
|
1197 |
+
|
1198 |
+
# الحصول على التاريخ الحالي بالعربية
|
1199 |
+
now = datetime.now()
|
1200 |
+
day = now.strftime("%d")
|
1201 |
+
try:
|
1202 |
+
month = now.strftime("%B")
|
1203 |
+
except:
|
1204 |
+
month = now.strftime("%m")
|
1205 |
+
year = now.strftime("%Y")
|
1206 |
+
|
1207 |
+
# عرض التاريخ
|
1208 |
+
st.markdown(f"""
|
1209 |
+
<div class="date-box">
|
1210 |
+
<div class="date-day">{day}</div>
|
1211 |
+
<div class="date-info">
|
1212 |
+
<div class="date-month">{month}</div>
|
1213 |
+
<div class="date-year">{year}</div>
|
1214 |
</div>
|
1215 |
+
</div>
|
1216 |
+
""", unsafe_allow_html=True)
|
1217 |
+
|
1218 |
+
st.markdown("<div class='sidebar-divider'></div>", unsafe_allow_html=True)
|
1219 |
+
|
1220 |
+
# القائمة الرئيسية
|
1221 |
+
selected = st.sidebar.radio(
|
1222 |
+
"القائمة الرئيسية",
|
1223 |
+
["الرئيسية", "المناقصات", "التحليلات", "التقارير", "المساعد الذكي", "استخراج المستندات", "الإعدادات"],
|
1224 |
+
format_func=lambda x: f"<i class='fas fa-{get_icon(x)}'></i> {x}",
|
1225 |
+
label_visibility="collapsed"
|
1226 |
+
)
|
1227 |
+
|
1228 |
+
# معلومات الإصدار
|
1229 |
+
st.markdown("""
|
1230 |
+
<div class="sidebar-footer">
|
1231 |
+
<p>إصدار 2.0.0</p>
|
1232 |
+
</div>
|
1233 |
+
""", unsafe_allow_html=True)
|
1234 |
|
1235 |
+
# عرض الصفحة المناسبة بناءً على الاختيار
|
1236 |
+
if selected == "الرئيسية":
|
1237 |
+
show_dashboard()
|
1238 |
+
elif selected == "المناقصات":
|
1239 |
+
show_tenders()
|
1240 |
+
elif selected == "التحليلات":
|
1241 |
+
show_analysis()
|
1242 |
+
elif selected == "التقارير":
|
1243 |
+
show_reports()
|
1244 |
+
elif selected == "المساعد الذكي":
|
1245 |
+
show_ai_assistant()
|
1246 |
+
elif selected == "استخراج المستندات":
|
1247 |
+
show_document_extraction()
|
1248 |
+
elif selected == "الإعدادات":
|
1249 |
+
show_settings()
|
1250 |
|
1251 |
+
# تشغيل التطبيق
|
1252 |
if __name__ == "__main__":
|
1253 |
+
pass
|
database/db_connector.py
CHANGED
@@ -36,26 +36,6 @@ def get_connection():
|
|
36 |
except Exception as e:
|
37 |
print(f"خطأ في الاتصال بقاعدة البيانات: {e}")
|
38 |
|
39 |
-
# إذا فشل الاتصال، استخدم اتصال قاعدة بيانات
|
40 |
-
import
|
41 |
-
|
42 |
-
|
43 |
-
# إنشاء مجلد البيانات إذا لم يكن موجوداً
|
44 |
-
data_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data')
|
45 |
-
os.makedirs(data_dir, exist_ok=True)
|
46 |
-
|
47 |
-
# إنشاء اتصال قاعدة بيانات SQLite محلية
|
48 |
-
db_path = os.path.join(data_dir, 'local_db.sqlite')
|
49 |
-
conn = sqlite3.connect(db_path)
|
50 |
-
|
51 |
-
# إعادة محاكاة سلوك اتصال PostgreSQL
|
52 |
-
conn.execute = conn.cursor().execute
|
53 |
-
|
54 |
-
# إضافة وظيفة وهمية للاقتطاع (commit) والإغلاق
|
55 |
-
original_close = conn.close
|
56 |
-
def enhanced_close():
|
57 |
-
conn.commit()
|
58 |
-
original_close()
|
59 |
-
conn.close = enhanced_close
|
60 |
-
|
61 |
-
return conn
|
|
|
36 |
except Exception as e:
|
37 |
print(f"خطأ في الاتصال بقاعدة البيانات: {e}")
|
38 |
|
39 |
+
# إذا فشل الاتصال، استخدم اتصال قاعدة بيانات وهمي
|
40 |
+
from utils.helpers import get_connection as get_mock_connection
|
41 |
+
return get_mock_connection()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/ai_assistant/ai_assistant.py
CHANGED
@@ -43,9 +43,10 @@ class AIAssistant:
|
|
43 |
# تهيئة مفتاح OpenAI API
|
44 |
self.openai_api_key = os.environ.get("OPENAI_API_KEY")
|
45 |
if self.openai_api_key:
|
46 |
-
openai.api_key
|
47 |
self.is_api_available = True
|
48 |
else:
|
|
|
49 |
self.is_api_available = False
|
50 |
|
51 |
# نموذج OpenAI المستخدم
|
@@ -164,7 +165,7 @@ class AIAssistant:
|
|
164 |
if model is None:
|
165 |
model = self.model
|
166 |
|
167 |
-
response =
|
168 |
model=model,
|
169 |
messages=messages,
|
170 |
max_tokens=max_tokens,
|
@@ -174,7 +175,10 @@ class AIAssistant:
|
|
174 |
presence_penalty=0
|
175 |
)
|
176 |
|
177 |
-
|
|
|
|
|
|
|
178 |
except Exception as e:
|
179 |
logging.error(f"خطأ في استدعاء OpenAI API: {e}")
|
180 |
return {
|
@@ -184,8 +188,10 @@ class AIAssistant:
|
|
184 |
def _call_backend_api(self, endpoint, data):
|
185 |
"""استدعاء واجهة API الخلفية للنظام"""
|
186 |
try:
|
|
|
|
|
187 |
response = requests.post(
|
188 |
-
f"
|
189 |
json=data,
|
190 |
timeout=60
|
191 |
)
|
@@ -198,576 +204,3 @@ class AIAssistant:
|
|
198 |
except Exception as e:
|
199 |
logging.error(f"خطأ في الاتصال بواجهة API الخلفية: {e}")
|
200 |
return {"error": f"خطأ في الاتصال بواجهة API الخلفية: {str(e)}"}
|
201 |
-
|
202 |
-
def _process_user_message(self, user_message, mode=None):
|
203 |
-
"""معالجة رسالة المستخدم والحصول على رد من المساعد الذكي"""
|
204 |
-
if mode is None:
|
205 |
-
mode = st.session_state.assistant_mode
|
206 |
-
|
207 |
-
# إنشاء قائمة الرسائل للمحادثة
|
208 |
-
messages = [
|
209 |
-
{"role": "system", "content": self.system_prompts[mode]}
|
210 |
-
]
|
211 |
-
|
212 |
-
# إضافة سياق المستند إذا كان متاحاً
|
213 |
-
if st.session_state.document_context:
|
214 |
-
messages.append({
|
215 |
-
"role": "system",
|
216 |
-
"content": f"معلومات سياقية عن المستند: {st.session_state.document_context}"
|
217 |
-
})
|
218 |
-
|
219 |
-
# إضافة المحادثة السابقة
|
220 |
-
for msg in st.session_state.assistant_messages:
|
221 |
-
messages.append({
|
222 |
-
"role": msg["role"],
|
223 |
-
"content": msg["content"]
|
224 |
-
})
|
225 |
-
|
226 |
-
# إضافة رسالة المستخدم الحالية
|
227 |
-
messages.append({
|
228 |
-
"role": "user",
|
229 |
-
"content": user_message
|
230 |
-
})
|
231 |
-
|
232 |
-
# استدعاء API
|
233 |
-
response = self._call_openai_api(messages)
|
234 |
-
|
235 |
-
# استخراج الرد
|
236 |
-
assistant_response = response["choices"][0]["message"]["content"]
|
237 |
-
|
238 |
-
# تحديث سجل المحادثة
|
239 |
-
st.session_state.assistant_messages.append({"role": "user", "content": user_message})
|
240 |
-
st.session_state.assistant_messages.append({"role": "assistant", "content": assistant_response})
|
241 |
-
|
242 |
-
return assistant_response
|
243 |
-
|
244 |
-
def _clear_chat(self):
|
245 |
-
"""مسح المحادثة الحالية"""
|
246 |
-
st.session_state.assistant_messages = []
|
247 |
-
st.session_state.document_context = None
|
248 |
-
|
249 |
-
def _save_conversation(self):
|
250 |
-
"""حفظ المحادثة الحالية"""
|
251 |
-
if not st.session_state.assistant_messages:
|
252 |
-
st.warning("لا توجد محادثة لحفظها.")
|
253 |
-
return False
|
254 |
-
|
255 |
-
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
256 |
-
user_info = get_user_info()
|
257 |
-
|
258 |
-
conversation_data = {
|
259 |
-
"timestamp": timestamp,
|
260 |
-
"user": user_info["username"],
|
261 |
-
"mode": st.session_state.assistant_mode,
|
262 |
-
"messages": st.session_state.assistant_messages,
|
263 |
-
"document_context": st.session_state.document_context
|
264 |
-
}
|
265 |
-
|
266 |
-
filename = f"conversation_{user_info['username']}_{timestamp}.json"
|
267 |
-
file_path = os.path.join(self.conversations_dir, filename)
|
268 |
-
|
269 |
-
try:
|
270 |
-
with open(file_path, 'w', encoding='utf-8') as f:
|
271 |
-
json.dump(conversation_data, f, ensure_ascii=False, indent=2)
|
272 |
-
|
273 |
-
return True
|
274 |
-
except Exception as e:
|
275 |
-
logging.error(f"خطأ في حفظ المحادثة: {e}")
|
276 |
-
return False
|
277 |
-
|
278 |
-
def _load_conversation(self, filename):
|
279 |
-
"""تحميل محادثة محفوظة"""
|
280 |
-
file_path = os.path.join(self.conversations_dir, filename)
|
281 |
-
|
282 |
-
try:
|
283 |
-
with open(file_path, 'r', encoding='utf-8') as f:
|
284 |
-
conversation_data = json.load(f)
|
285 |
-
|
286 |
-
st.session_state.assistant_messages = conversation_data["messages"]
|
287 |
-
st.session_state.assistant_mode = conversation_data["mode"]
|
288 |
-
st.session_state.document_context = conversation_data.get("document_context")
|
289 |
-
|
290 |
-
return True
|
291 |
-
except Exception as e:
|
292 |
-
logging.error(f"خطأ في تحميل المحادثة: {e}")
|
293 |
-
return False
|
294 |
-
|
295 |
-
def _get_saved_conversations(self):
|
296 |
-
"""الحصول على قائمة المحادثات المحفوظة"""
|
297 |
-
conversations = []
|
298 |
-
|
299 |
-
try:
|
300 |
-
for filename in os.listdir(self.conversations_dir):
|
301 |
-
if filename.endswith(".json") and filename.startswith("conversation_"):
|
302 |
-
file_path = os.path.join(self.conversations_dir, filename)
|
303 |
-
|
304 |
-
with open(file_path, 'r', encoding='utf-8') as f:
|
305 |
-
data = json.load(f)
|
306 |
-
|
307 |
-
conversations.append({
|
308 |
-
"filename": filename,
|
309 |
-
"timestamp": data.get("timestamp", ""),
|
310 |
-
"user": data.get("user", ""),
|
311 |
-
"mode": data.get("mode", "general"),
|
312 |
-
"message_count": len(data.get("messages", []))
|
313 |
-
})
|
314 |
-
except Exception as e:
|
315 |
-
logging.error(f"خطأ في قراءة المحادثات المحفوظة: {e}")
|
316 |
-
|
317 |
-
# ترتيب المحادثات حسب التاريخ (الأحدث أولاً)
|
318 |
-
conversations.sort(key=lambda x: x["timestamp"], reverse=True)
|
319 |
-
|
320 |
-
return conversations
|
321 |
-
|
322 |
-
def render_chat_interface(self):
|
323 |
-
"""عرض واجهة المحادثة الرئيسية"""
|
324 |
-
st.markdown("<h2 class='module-title'>المساعد الذكي</h2>", unsafe_allow_html=True)
|
325 |
-
|
326 |
-
# التحقق من توفر OpenAI API
|
327 |
-
if not self.is_api_available:
|
328 |
-
st.warning("⚠️ مفتاح OpenAI API غير متوفر. لن يكون المساعد الذكي قادراً على الرد. يرجى التواصل مع مسؤول النظام.")
|
329 |
-
|
330 |
-
# إضافة CSS
|
331 |
-
st.markdown("""
|
332 |
-
<style>
|
333 |
-
.chat-container {
|
334 |
-
background-color: #f8f9fa;
|
335 |
-
border-radius: 10px;
|
336 |
-
padding: 20px;
|
337 |
-
margin-bottom: 20px;
|
338 |
-
max-height: 500px;
|
339 |
-
overflow-y: auto;
|
340 |
-
}
|
341 |
-
|
342 |
-
.chat-message {
|
343 |
-
margin-bottom: 15px;
|
344 |
-
display: flex;
|
345 |
-
flex-direction: row;
|
346 |
-
}
|
347 |
-
|
348 |
-
.user-message {
|
349 |
-
justify-content: flex-end;
|
350 |
-
}
|
351 |
-
|
352 |
-
.assistant-message {
|
353 |
-
justify-content: flex-start;
|
354 |
-
}
|
355 |
-
|
356 |
-
.message-bubble {
|
357 |
-
padding: 10px 15px;
|
358 |
-
border-radius: 15px;
|
359 |
-
max-width: 75%;
|
360 |
-
}
|
361 |
-
|
362 |
-
.user-bubble {
|
363 |
-
background-color: #1E88E5;
|
364 |
-
color: white;
|
365 |
-
border-top-left-radius: 15px;
|
366 |
-
border-top-right-radius: 15px;
|
367 |
-
border-bottom-left-radius: 15px;
|
368 |
-
border-bottom-right-radius: 0;
|
369 |
-
}
|
370 |
-
|
371 |
-
.assistant-bubble {
|
372 |
-
background-color: #f0f0f0;
|
373 |
-
color: #333;
|
374 |
-
border-top-left-radius: 15px;
|
375 |
-
border-top-right-radius: 15px;
|
376 |
-
border-bottom-left-radius: 0;
|
377 |
-
border-bottom-right-radius: 15px;
|
378 |
-
}
|
379 |
-
|
380 |
-
.message-avatar {
|
381 |
-
width: 40px;
|
382 |
-
height: 40px;
|
383 |
-
border-radius: 50%;
|
384 |
-
background-color: #ccc;
|
385 |
-
display: flex;
|
386 |
-
align-items: center;
|
387 |
-
justify-content: center;
|
388 |
-
margin: 0 10px;
|
389 |
-
font-weight: bold;
|
390 |
-
color: white;
|
391 |
-
}
|
392 |
-
|
393 |
-
.user-avatar {
|
394 |
-
background-color: #78909C;
|
395 |
-
}
|
396 |
-
|
397 |
-
.assistant-avatar {
|
398 |
-
background-color: #1E88E5;
|
399 |
-
}
|
400 |
-
|
401 |
-
.message-content {
|
402 |
-
white-space: pre-wrap;
|
403 |
-
}
|
404 |
-
|
405 |
-
.message-time {
|
406 |
-
font-size: 0.8em;
|
407 |
-
color: #888;
|
408 |
-
margin-top: 5px;
|
409 |
-
text-align: right;
|
410 |
-
}
|
411 |
-
|
412 |
-
.chat-input {
|
413 |
-
background-color: #f8f9fa;
|
414 |
-
border-radius: 10px;
|
415 |
-
padding: 20px;
|
416 |
-
}
|
417 |
-
|
418 |
-
.suggestions-container {
|
419 |
-
display: flex;
|
420 |
-
flex-wrap: wrap;
|
421 |
-
gap: 10px;
|
422 |
-
margin-top: 10px;
|
423 |
-
}
|
424 |
-
|
425 |
-
.suggestion-chip {
|
426 |
-
background-color: #e9ecef;
|
427 |
-
border-radius: 20px;
|
428 |
-
padding: 5px 15px;
|
429 |
-
cursor: pointer;
|
430 |
-
text-align: center;
|
431 |
-
transition: background-color 0.3s;
|
432 |
-
}
|
433 |
-
|
434 |
-
.suggestion-chip:hover {
|
435 |
-
background-color: #dee2e6;
|
436 |
-
}
|
437 |
-
</style>
|
438 |
-
""", unsafe_allow_html=True)
|
439 |
-
|
440 |
-
# عرض أوضاع المساعد
|
441 |
-
st.markdown("#### اختر وضع المساعد الذكي")
|
442 |
-
|
443 |
-
col1, col2, col3, col4, col5 = st.columns(5)
|
444 |
-
|
445 |
-
with col1:
|
446 |
-
if st.button("مساعد عام", key="mode_general",
|
447 |
-
help="مساعد عام للإجابة على الأسئلة المتعلقة بالعقود والمناقصات"):
|
448 |
-
st.session_state.assistant_mode = "general"
|
449 |
-
st.rerun()
|
450 |
-
|
451 |
-
with col2:
|
452 |
-
if st.button("تحليل العقود", key="mode_contract_analysis",
|
453 |
-
help="متخصص في تحليل العقود وتحديد البنود والشروط والمخاطر"):
|
454 |
-
st.session_state.assistant_mode = "contract_analysis"
|
455 |
-
st.rerun()
|
456 |
-
|
457 |
-
with col3:
|
458 |
-
if st.button("تقدير التكاليف", key="mode_cost_estimation",
|
459 |
-
help="متخصص في تقدير تكاليف المشاريع والبنود"):
|
460 |
-
st.session_state.assistant_mode = "cost_estimation"
|
461 |
-
st.rerun()
|
462 |
-
|
463 |
-
with col4:
|
464 |
-
if st.button("تقييم المخاطر", key="mode_risk_assessment",
|
465 |
-
help="متخصص في تحديد وتقييم المخاطر المحتملة في المشاريع والعقود"):
|
466 |
-
st.session_state.assistant_mode = "risk_assessment"
|
467 |
-
st.rerun()
|
468 |
-
|
469 |
-
with col5:
|
470 |
-
if st.button("تخطيط المشاريع", key="mode_project_planning",
|
471 |
-
help="متخصص في تخطيط وإدارة المشاريع وتحديد المراحل والموارد"):
|
472 |
-
st.session_state.assistant_mode = "project_planning"
|
473 |
-
st.rerun()
|
474 |
-
|
475 |
-
st.markdown(f"**الوضع الحالي:** {self.assistant_modes[st.session_state.assistant_mode]}")
|
476 |
-
|
477 |
-
# تحميل سياق من مستند (اختياري)
|
478 |
-
st.markdown("---")
|
479 |
-
with st.expander("إضافة سياق من مستند", expanded=False):
|
480 |
-
context_text = st.text_area(
|
481 |
-
"نص المستند (اختياري)",
|
482 |
-
value=st.session_state.document_context if st.session_state.document_context else "",
|
483 |
-
height=150,
|
484 |
-
help="أضف نص المستند هنا ليتم استخدامه كسياق للمحادثة"
|
485 |
-
)
|
486 |
-
|
487 |
-
uploaded_file = st.file_uploader(
|
488 |
-
"أو قم بتحميل ملف نصي أو PDF",
|
489 |
-
type=["txt", "pdf"],
|
490 |
-
help="يمكنك تحميل ملف نصي أو PDF ليتم استخدامه كسياق للمحادثة"
|
491 |
-
)
|
492 |
-
|
493 |
-
doc_col1, doc_col2 = st.columns(2)
|
494 |
-
|
495 |
-
with doc_col1:
|
496 |
-
if st.button("إضافة السياق", disabled=not context_text and not uploaded_file):
|
497 |
-
if uploaded_file:
|
498 |
-
try:
|
499 |
-
# قراءة الملف المرفوع
|
500 |
-
if uploaded_file.name.endswith(".pdf"):
|
501 |
-
import PyPDF2
|
502 |
-
reader = PyPDF2.PdfReader(uploaded_file)
|
503 |
-
context = ""
|
504 |
-
for page in reader.pages:
|
505 |
-
context += page.extract_text() + "\n"
|
506 |
-
else:
|
507 |
-
context = uploaded_file.read().decode("utf-8")
|
508 |
-
|
509 |
-
st.session_state.document_context = context
|
510 |
-
st.success("تم إضافة نص المستند كسياق للمحادثة بنجاح.")
|
511 |
-
except Exception as e:
|
512 |
-
st.error(f"حدث خطأ أثناء قراءة الملف: {str(e)}")
|
513 |
-
elif context_text:
|
514 |
-
st.session_state.document_context = context_text
|
515 |
-
st.success("تم إضافة نص المستند كسياق للمحادثة بنجاح.")
|
516 |
-
|
517 |
-
with doc_col2:
|
518 |
-
if st.button("مسح السياق", disabled=not st.session_state.document_context):
|
519 |
-
st.session_state.document_context = None
|
520 |
-
st.success("تم مسح سياق المستند بنجاح.")
|
521 |
-
|
522 |
-
# عرض المحادثة
|
523 |
-
st.markdown("---")
|
524 |
-
st.markdown("#### المحادثة مع المساعد الذكي")
|
525 |
-
|
526 |
-
# عرض رسائل المحادثة
|
527 |
-
chat_container = st.container()
|
528 |
-
|
529 |
-
with chat_container:
|
530 |
-
with st.container():
|
531 |
-
if not st.session_state.assistant_messages:
|
532 |
-
st.markdown("""
|
533 |
-
<div style="text-align: center; padding: 30px; color: #666;">
|
534 |
-
<p>مرحباً بك في المساعد الذكي!</p>
|
535 |
-
<p>يمكنك البدء بطرح سؤال أو طلب مساعدة.</p>
|
536 |
-
</div>
|
537 |
-
""", unsafe_allow_html=True)
|
538 |
-
else:
|
539 |
-
message_html = ""
|
540 |
-
|
541 |
-
for msg in st.session_state.assistant_messages:
|
542 |
-
if msg["role"] == "user":
|
543 |
-
message_html += f"""
|
544 |
-
<div class="chat-message user-message">
|
545 |
-
<div class="message-bubble user-bubble">
|
546 |
-
<div class="message-content">{msg["content"]}</div>
|
547 |
-
</div>
|
548 |
-
<div class="message-avatar user-avatar">أ</div>
|
549 |
-
</div>
|
550 |
-
"""
|
551 |
-
else:
|
552 |
-
message_html += f"""
|
553 |
-
<div class="chat-message assistant-message">
|
554 |
-
<div class="message-avatar assistant-avatar">W</div>
|
555 |
-
<div class="message-bubble assistant-bubble">
|
556 |
-
<div class="message-content">{msg["content"]}</div>
|
557 |
-
</div>
|
558 |
-
</div>
|
559 |
-
"""
|
560 |
-
|
561 |
-
st.markdown(f"""
|
562 |
-
<div class="chat-container">
|
563 |
-
{message_html}
|
564 |
-
</div>
|
565 |
-
""", unsafe_allow_html=True)
|
566 |
-
|
567 |
-
# ادخال الرسالة
|
568 |
-
st.markdown("#### أدخل رسالتك")
|
569 |
-
|
570 |
-
with st.container():
|
571 |
-
with st.form(key="chat_form"):
|
572 |
-
user_message = st.text_area("رسالتك", height=100, placeholder="اكتب سؤالك أو طلبك هنا...")
|
573 |
-
|
574 |
-
col1, col2, col3 = st.columns([2, 2, 1])
|
575 |
-
|
576 |
-
with col1:
|
577 |
-
send_button = st.form_submit_button(
|
578 |
-
"إرسال",
|
579 |
-
help="إرسال الرسالة إلى المساعد الذكي"
|
580 |
-
)
|
581 |
-
|
582 |
-
with col2:
|
583 |
-
suggested_questions = [
|
584 |
-
"كيف يمكنني تحليل بنود الدفع في العقد؟",
|
585 |
-
"ما هي أفضل طريقة لتقدير تكاليف مشروع بناء؟",
|
586 |
-
"كيف أحدد المخاطر المحتملة في مشروع جديد؟",
|
587 |
-
"كيف يمكنني إنشاء جدول زمني فعال للمشروع؟",
|
588 |
-
"ما هي أهم البنود التي يجب الانتباه إليها في عقود المقاولات؟"
|
589 |
-
]
|
590 |
-
|
591 |
-
if st.session_state.assistant_mode == "contract_analysis":
|
592 |
-
suggested_questions = [
|
593 |
-
"كيف أحدد البنود غير المواتية في العقد؟",
|
594 |
-
"ما هي العناصر الأساسية التي يجب أن يتضمنها عقد المقاولة؟",
|
595 |
-
"كيف أتعامل مع بنود الغرامات والتعويضات؟",
|
596 |
-
"كيف يمكنني التفاوض على تحسين شروط الدفع؟",
|
597 |
-
"ما هي الفروق الرئيسية بين عقد الثمن الثابت وعقد التكلفة زائد أتعاب؟"
|
598 |
-
]
|
599 |
-
elif st.session_state.assistant_mode == "cost_estimation":
|
600 |
-
suggested_questions = [
|
601 |
-
"كيف أقدر تكلفة المواد في مشروع بناء؟",
|
602 |
-
"ما هي نسبة النفقات العامة المعقولة لمشروع مقاولات؟",
|
603 |
-
"كيف أحسب تكلفة العمالة بدقة؟",
|
604 |
-
"ما هي العوامل التي تؤثر على تكلفة المعدات؟",
|
605 |
-
"كيف أقدر هامش الربح المناسب للمشروع؟"
|
606 |
-
]
|
607 |
-
|
608 |
-
selected_question = st.selectbox(
|
609 |
-
"أو اختر سؤال مقترح",
|
610 |
-
[""] + suggested_questions,
|
611 |
-
index=0
|
612 |
-
)
|
613 |
-
|
614 |
-
with col3:
|
615 |
-
clear_button = st.form_submit_button(
|
616 |
-
"مسح المحادثة",
|
617 |
-
help="مسح جميع الرسائل في المحادثة الحالية"
|
618 |
-
)
|
619 |
-
|
620 |
-
if send_button and user_message:
|
621 |
-
# معالجة رسالة المستخدم
|
622 |
-
with st.spinner("جاري معالجة الرسالة..."):
|
623 |
-
self._process_user_message(user_message)
|
624 |
-
st.rerun()
|
625 |
-
|
626 |
-
if send_button and selected_question and not user_message:
|
627 |
-
# استخدام السؤال المقترح
|
628 |
-
with st.spinner("جاري معالجة الرسالة..."):
|
629 |
-
self._process_user_message(selected_question)
|
630 |
-
st.rerun()
|
631 |
-
|
632 |
-
if clear_button:
|
633 |
-
self._clear_chat()
|
634 |
-
st.rerun()
|
635 |
-
|
636 |
-
# زر لحفظ المحادثة
|
637 |
-
col1, col2, col3 = st.columns([1, 1, 2])
|
638 |
-
|
639 |
-
with col1:
|
640 |
-
if st.button("حفظ المحادثة", key="save_conversation", disabled=not st.session_state.assistant_messages):
|
641 |
-
if self._save_conversation():
|
642 |
-
st.success("تم حفظ المحادثة بنجاح.")
|
643 |
-
else:
|
644 |
-
st.error("حدث خطأ أثناء حفظ المحادثة.")
|
645 |
-
|
646 |
-
with col2:
|
647 |
-
if st.button("تحميل محادثة سابقة", key="show_load_conversation"):
|
648 |
-
st.session_state.show_conversations = True
|
649 |
-
st.rerun()
|
650 |
-
|
651 |
-
# عرض المحادثات المحفوظة
|
652 |
-
if "show_conversations" in st.session_state and st.session_state.show_conversations:
|
653 |
-
st.markdown("---")
|
654 |
-
st.markdown("#### المحادثات المحفوظة")
|
655 |
-
|
656 |
-
conversations = self._get_saved_conversations()
|
657 |
-
|
658 |
-
if not conversations:
|
659 |
-
st.info("لا توجد محادثات محفوظة.")
|
660 |
-
else:
|
661 |
-
# عرض المحادثات في جدول
|
662 |
-
conversation_data = []
|
663 |
-
for conv in conversations:
|
664 |
-
timestamp = datetime.strptime(conv["timestamp"], "%Y%m%d%H%M%S") if conv["timestamp"] else ""
|
665 |
-
formatted_time = timestamp.strftime("%Y-%m-%d %H:%M:%S") if timestamp else ""
|
666 |
-
|
667 |
-
conversation_data.append({
|
668 |
-
"التاريخ": formatted_time,
|
669 |
-
"المستخدم": conv["user"],
|
670 |
-
"وضع المساعد": self.assistant_modes.get(conv["mode"], "غير معروف"),
|
671 |
-
"عدد الرسائل": conv["message_count"],
|
672 |
-
"الملف": conv["filename"]
|
673 |
-
})
|
674 |
-
|
675 |
-
df = pd.DataFrame(conversation_data)
|
676 |
-
st.dataframe(df, height=300)
|
677 |
-
|
678 |
-
# اختيار محادثة لتحميلها
|
679 |
-
selected_filename = st.selectbox(
|
680 |
-
"اختر محادثة لتحميلها",
|
681 |
-
options=[""] + [conv["filename"] for conv in conversations],
|
682 |
-
format_func=lambda x: next((f"{c['user']} - {datetime.strptime(c['timestamp'], '%Y%m%d%H%M%S').strftime('%Y-%m-%d %H:%M:%S')}" for c in conversations if c["filename"] == x), x),
|
683 |
-
index=0
|
684 |
-
)
|
685 |
-
|
686 |
-
col1, col2 = st.columns(2)
|
687 |
-
|
688 |
-
with col1:
|
689 |
-
if st.button("تحميل المحادثة المختارة", disabled=not selected_filename):
|
690 |
-
if self._load_conversation(selected_filename):
|
691 |
-
st.success("تم تحميل المحادثة بنجاح.")
|
692 |
-
st.session_state.show_conversations = False
|
693 |
-
st.rerun()
|
694 |
-
else:
|
695 |
-
st.error("حدث خطأ أثناء تحميل المحادثة.")
|
696 |
-
|
697 |
-
with col2:
|
698 |
-
if st.button("إلغاء", key="cancel_load_conversation"):
|
699 |
-
st.session_state.show_conversations = False
|
700 |
-
st.rerun()
|
701 |
-
|
702 |
-
# عرض المعلومات عن وضع المساعد الحالي
|
703 |
-
st.markdown("---")
|
704 |
-
st.markdown(f"#### معلومات عن وضع المساعد: {self.assistant_modes[st.session_state.assistant_mode]}")
|
705 |
-
|
706 |
-
if st.session_state.assistant_mode == "general":
|
707 |
-
st.markdown("""
|
708 |
-
المساعد العام يمكنه مساعدتك في مجموعة متنوعة من المهام المتعلقة بالعقود والمناقصات وإدارة المشاريع. يمكنه:
|
709 |
-
- الإجابة على الأسئلة العامة حول العقود والمناقصات
|
710 |
-
- توجيهك إلى الوحدات المناسبة في النظام
|
711 |
-
- تقديم معلومات عامة عن إدارة المشاريع وأفضل الممارسات
|
712 |
-
- المساعدة في فهم المصطلحات والمفاهيم المتعلقة بمجال المقاولات
|
713 |
-
""")
|
714 |
-
elif st.session_state.assistant_mode == "contract_analysis":
|
715 |
-
st.markdown("""
|
716 |
-
مساعد تحليل العقود متخصص في:
|
717 |
-
- تحليل بنود العقود وتوضيح معانيها
|
718 |
-
- تحديد الالتزامات والحقوق لكل طرف
|
719 |
-
- تسليط الضوء على البنود غير المواتية أو الغامضة
|
720 |
-
- تقديم توصيات للتفاوض على تحسين شروط العقد
|
721 |
-
- مقارنة العقد مع أفضل الممارسات في القطاع
|
722 |
-
""")
|
723 |
-
elif st.session_state.assistant_mode == "cost_estimation":
|
724 |
-
st.markdown("""
|
725 |
-
مساعد تقدير التكاليف متخصص في:
|
726 |
-
- حساب تكاليف المشاريع بناءً على المتطلبات والمواصفات
|
727 |
-
- تقدير تكاليف المواد والعمالة والمعدات
|
728 |
-
- تحليل التكاليف المباشرة وغير المباشرة
|
729 |
-
- تقديم نصائح لتقليل التكاليف وزيادة الكفاءة
|
730 |
-
- تحديد العوامل التي قد تؤثر على التكلفة الإجمالية
|
731 |
-
""")
|
732 |
-
elif st.session_state.assistant_mode == "risk_assessment":
|
733 |
-
st.markdown("""
|
734 |
-
مساعد تقييم المخاطر متخصص في:
|
735 |
-
- تحديد المخاطر المحتملة في المشاريع والعقود
|
736 |
-
- تقييم احتمالية وتأثير كل خطر
|
737 |
-
- اقتراح استراتيجيات للتخفيف من المخاطر
|
738 |
-
- إنشاء خطط للطوارئ والاستجابة للمخاطر
|
739 |
-
- تحليل تأثير المخاطر على الجدول الزمني والتكلفة
|
740 |
-
""")
|
741 |
-
elif st.session_state.assistant_mode == "project_planning":
|
742 |
-
st.markdown("""
|
743 |
-
مساعد تخطيط المشاريع متخصص في:
|
744 |
-
- تقسيم المشروع إلى مراحل ومهام وأنشطة
|
745 |
-
- تحديد الموارد اللازمة لكل نشاط
|
746 |
-
- إنشاء الجداول الزمنية والمسار الحرج
|
747 |
-
- التخطيط للموارد البشرية والمعدات والمواد
|
748 |
-
- مراقبة تقدم المشروع وإدارة التغييرات
|
749 |
-
""")
|
750 |
-
|
751 |
-
# عرض معلومات حقوق الملكية
|
752 |
-
render_credits()
|
753 |
-
|
754 |
-
def render(self):
|
755 |
-
"""عرض واجهة المساعد الذكي الرئيسية"""
|
756 |
-
# تحميل CSS المخصص
|
757 |
-
load_css()
|
758 |
-
|
759 |
-
# عرض واجهة المحادثة
|
760 |
-
self.render_chat_interface()
|
761 |
-
|
762 |
-
|
763 |
-
# تشغيل التطبيق بشكل مستقل عند استدعاء الملف مباشرة
|
764 |
-
if __name__ == "__main__":
|
765 |
-
st.set_page_config(
|
766 |
-
page_title="المساعد الذكي | WAHBi AI",
|
767 |
-
page_icon="🤖",
|
768 |
-
layout="wide",
|
769 |
-
initial_sidebar_state="expanded"
|
770 |
-
)
|
771 |
-
|
772 |
-
assistant = AIAssistant()
|
773 |
-
assistant.render()
|
|
|
43 |
# تهيئة مفتاح OpenAI API
|
44 |
self.openai_api_key = os.environ.get("OPENAI_API_KEY")
|
45 |
if self.openai_api_key:
|
46 |
+
self.client = openai.OpenAI(api_key=self.openai_api_key)
|
47 |
self.is_api_available = True
|
48 |
else:
|
49 |
+
self.client = None
|
50 |
self.is_api_available = False
|
51 |
|
52 |
# نموذج OpenAI المستخدم
|
|
|
165 |
if model is None:
|
166 |
model = self.model
|
167 |
|
168 |
+
response = self.client.chat.completions.create(
|
169 |
model=model,
|
170 |
messages=messages,
|
171 |
max_tokens=max_tokens,
|
|
|
175 |
presence_penalty=0
|
176 |
)
|
177 |
|
178 |
+
# تحويل الاستجابة إلى تنسيق متوافق مع الكود القديم
|
179 |
+
return {
|
180 |
+
"choices": [{"message": {"content": response.choices[0].message.content}}]
|
181 |
+
}
|
182 |
except Exception as e:
|
183 |
logging.error(f"خطأ في استدعاء OpenAI API: {e}")
|
184 |
return {
|
|
|
188 |
def _call_backend_api(self, endpoint, data):
|
189 |
"""استدعاء واجهة API الخلفية للنظام"""
|
190 |
try:
|
191 |
+
# تغيير المنفذ من 5000 إلى 8000 لتجنب التعارض مع الخدمات الأخرى
|
192 |
+
api_url = os.environ.get("BACKEND_API_URL", "http://localhost:8000/api")
|
193 |
response = requests.post(
|
194 |
+
f"{api_url}/{endpoint}",
|
195 |
json=data,
|
196 |
timeout=60
|
197 |
)
|
|
|
204 |
except Exception as e:
|
205 |
logging.error(f"خطأ في الاتصال بواجهة API الخلفية: {e}")
|
206 |
return {"error": f"خطأ في الاتصال بواجهة API الخلفية: {str(e)}"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/document_comparison/document_comparator.py
CHANGED
@@ -38,37 +38,8 @@ class DocumentComparator:
|
|
38 |
self.comparison_dir = os.path.join(os.path.dirname(__file__), '..', '..', 'data', 'document_comparison')
|
39 |
create_directory_if_not_exists(self.comparison_dir)
|
40 |
|
41 |
-
# تهيئة NLTK وتنزيل حزمة punkt إذا لم تكن موجودة
|
42 |
-
self._initialize_nltk()
|
43 |
-
|
44 |
# إعداد مقيم ROUGE لمقارنة النصوص
|
45 |
self.rouge_scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=False)
|
46 |
-
|
47 |
-
def _initialize_nltk(self):
|
48 |
-
"""تهيئة مكتبة NLTK وتنزيل الحزم المطلوبة"""
|
49 |
-
try:
|
50 |
-
# استيراد nltk
|
51 |
-
import nltk
|
52 |
-
|
53 |
-
# قائمة بالحزم المطلوبة
|
54 |
-
required_packages = ['punkt', 'stopwords', 'wordnet']
|
55 |
-
for package in required_packages:
|
56 |
-
try:
|
57 |
-
# محاولة استخدام الحزمة أولاً، وإذا فشلت يتم تنزيلها
|
58 |
-
nltk.data.find(f'tokenizers/{package}')
|
59 |
-
except LookupError:
|
60 |
-
print(f"تنزيل حزمة NLTK: {package}")
|
61 |
-
nltk.download(package, quiet=True)
|
62 |
-
|
63 |
-
# محاولة استخدام sent_tokenize للتحقق من وجود حزمة punkt
|
64 |
-
from nltk.tokenize import sent_tokenize
|
65 |
-
sent_tokenize("This is a test sentence.")
|
66 |
-
except LookupError:
|
67 |
-
# تنزيل حزمة punkt تلقائيًا إذا لم تكن موجودة
|
68 |
-
import nltk
|
69 |
-
nltk.download('punkt', quiet=True)
|
70 |
-
# طباعة رسالة تأكيد التنزيل
|
71 |
-
st.info("تم تنزيل حزمة NLTK punkt بنجاح للاستخدام في مقارنة المستندات.")
|
72 |
|
73 |
def _preprocess_text(self, text):
|
74 |
"""معالجة النص قبل التحليل"""
|
|
|
38 |
self.comparison_dir = os.path.join(os.path.dirname(__file__), '..', '..', 'data', 'document_comparison')
|
39 |
create_directory_if_not_exists(self.comparison_dir)
|
40 |
|
|
|
|
|
|
|
41 |
# إعداد مقيم ROUGE لمقارنة النصوص
|
42 |
self.rouge_scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
|
44 |
def _preprocess_text(self, text):
|
45 |
"""معالجة النص قبل التحليل"""
|
modules/pricing/construction_calculator.py
CHANGED
@@ -19,18 +19,6 @@ def render_construction_calculator():
|
|
19 |
"""
|
20 |
عرض حاسبة تكاليف البناء المتكاملة
|
21 |
"""
|
22 |
-
# التأكد من وجود المتغيرات في حالة الجلسة
|
23 |
-
if 'materials_cost' not in st.session_state:
|
24 |
-
st.session_state.materials_cost = 0.0
|
25 |
-
if 'equipment_cost' not in st.session_state:
|
26 |
-
st.session_state.equipment_cost = 0.0
|
27 |
-
if 'labor_cost' not in st.session_state:
|
28 |
-
st.session_state.labor_cost = 0.0
|
29 |
-
if 'admin_cost' not in st.session_state:
|
30 |
-
st.session_state.admin_cost = 0.0
|
31 |
-
if 'profit_margin' not in st.session_state:
|
32 |
-
st.session_state.profit_margin = 15.0
|
33 |
-
|
34 |
st.markdown("<h2 class='module-title'>حاسبة تكاليف البناء المتكاملة</h2>", unsafe_allow_html=True)
|
35 |
|
36 |
# معلومات المشروع
|
@@ -651,30 +639,6 @@ def render_final_report(project_name, project_location, project_area, project_ty
|
|
651 |
"""
|
652 |
st.markdown("<h3>التقرير النهائي لتكاليف المشروع</h3>", unsafe_allow_html=True)
|
653 |
|
654 |
-
# التأكد من وجود المتغيرات المطلوبة في حالة الجلسة وضمان أن لديهم قيم صحيحة
|
655 |
-
required_fields = {
|
656 |
-
'materials_cost': 0.0,
|
657 |
-
'equipment_cost': 0.0,
|
658 |
-
'labor_cost': 0.0,
|
659 |
-
'admin_cost': 0.0,
|
660 |
-
'profit_margin': 15.0,
|
661 |
-
'materials': [],
|
662 |
-
'equipment': [],
|
663 |
-
'labor': [],
|
664 |
-
'admin_expenses': []
|
665 |
-
}
|
666 |
-
|
667 |
-
# مرور على كافة الحقول المطلوبة للتأكد من وجودها
|
668 |
-
for field, default_value in required_fields.items():
|
669 |
-
if field not in st.session_state:
|
670 |
-
st.session_state[field] = default_value
|
671 |
-
|
672 |
-
# التحقق من أن القيم العددية صالحة (غير None وليست NaN)
|
673 |
-
if field in ['materials_cost', 'equipment_cost', 'labor_cost', 'admin_cost', 'profit_margin']:
|
674 |
-
# إذا كانت القيمة None أو NaN، استخدم القيمة الافتراضية
|
675 |
-
if st.session_state[field] is None or pd.isna(st.session_state[field]):
|
676 |
-
st.session_state[field] = default_value
|
677 |
-
|
678 |
# حساب التكاليف المباشرة والإجمالية
|
679 |
direct_costs = st.session_state.materials_cost + st.session_state.equipment_cost + st.session_state.labor_cost
|
680 |
total_costs = direct_costs + st.session_state.admin_cost
|
@@ -717,39 +681,23 @@ def render_final_report(project_name, project_location, project_area, project_ty
|
|
717 |
st.markdown("</div>", unsafe_allow_html=True)
|
718 |
|
719 |
# عرض التفاصيل بالمتر المربع
|
720 |
-
|
721 |
-
|
722 |
-
|
723 |
-
|
724 |
-
|
725 |
-
|
726 |
-
else:
|
727 |
-
st.markdown("<div class='card'>", unsafe_allow_html=True)
|
728 |
-
st.markdown("<h4>تكلفة المتر المربع</h4>", unsafe_allow_html=True)
|
729 |
-
st.markdown("<p>يرجى إدخال مساحة صحيحة للمشروع لحساب تكلفة المتر المربع</p>", unsafe_allow_html=True)
|
730 |
-
st.markdown("</div>", unsafe_allow_html=True)
|
731 |
|
732 |
# رسم بياني لتوزيع التكاليف
|
733 |
st.markdown("<h4>توزيع التكاليف</h4>", unsafe_allow_html=True)
|
734 |
|
735 |
-
|
736 |
-
|
737 |
-
|
738 |
-
|
739 |
-
|
740 |
-
|
741 |
-
|
742 |
-
{"النوع": "هامش الربح", "القيمة": profit_amount, "النسبة": profit_amount / total_price * 100}
|
743 |
-
]
|
744 |
-
else:
|
745 |
-
# إذا كان المجموع صفر، اجعل جميع النسب المئوية صفر
|
746 |
-
cost_distribution = [
|
747 |
-
{"النوع": "المواد", "القيمة": st.session_state.materials_cost, "النسبة": 0},
|
748 |
-
{"النوع": "المعدات", "القيمة": st.session_state.equipment_cost, "النسبة": 0},
|
749 |
-
{"النوع": "العمالة", "القيمة": st.session_state.labor_cost, "النسبة": 0},
|
750 |
-
{"النوع": "المصاريف الإدارية", "القيمة": st.session_state.admin_cost, "النسبة": 0},
|
751 |
-
{"النوع": "هامش الربح", "القيمة": profit_amount, "النسبة": 0}
|
752 |
-
]
|
753 |
|
754 |
cost_df = pd.DataFrame(cost_distribution)
|
755 |
|
|
|
19 |
"""
|
20 |
عرض حاسبة تكاليف البناء المتكاملة
|
21 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
st.markdown("<h2 class='module-title'>حاسبة تكاليف البناء المتكاملة</h2>", unsafe_allow_html=True)
|
23 |
|
24 |
# معلومات المشروع
|
|
|
639 |
"""
|
640 |
st.markdown("<h3>التقرير النهائي لتكاليف المشروع</h3>", unsafe_allow_html=True)
|
641 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
642 |
# حساب التكاليف المباشرة والإجمالية
|
643 |
direct_costs = st.session_state.materials_cost + st.session_state.equipment_cost + st.session_state.labor_cost
|
644 |
total_costs = direct_costs + st.session_state.admin_cost
|
|
|
681 |
st.markdown("</div>", unsafe_allow_html=True)
|
682 |
|
683 |
# عرض التفاصيل بالمتر المربع
|
684 |
+
per_sqm_cost = total_price / project_area
|
685 |
+
|
686 |
+
st.markdown("<div class='card'>", unsafe_allow_html=True)
|
687 |
+
st.markdown("<h4>تكلفة المتر المربع</h4>", unsafe_allow_html=True)
|
688 |
+
st.markdown(f"<p>تكلفة المتر المربع الإجمالية: <strong>{per_sqm_cost:,.2f} ريال/م²</strong></p>", unsafe_allow_html=True)
|
689 |
+
st.markdown("</div>", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
690 |
|
691 |
# رسم بياني لتوزيع التكاليف
|
692 |
st.markdown("<h4>توزيع التكاليف</h4>", unsafe_allow_html=True)
|
693 |
|
694 |
+
cost_distribution = [
|
695 |
+
{"النوع": "المواد", "القيمة": st.session_state.materials_cost, "النسبة": st.session_state.materials_cost / total_price * 100},
|
696 |
+
{"النوع": "المعدات", "القيمة": st.session_state.equipment_cost, "النسبة": st.session_state.equipment_cost / total_price * 100},
|
697 |
+
{"النوع": "العمالة", "القيمة": st.session_state.labor_cost, "النسبة": st.session_state.labor_cost / total_price * 100},
|
698 |
+
{"النوع": "المصاريف الإدارية", "القيمة": st.session_state.admin_cost, "النسبة": st.session_state.admin_cost / total_price * 100},
|
699 |
+
{"النوع": "هامش الربح", "القيمة": profit_amount, "النسبة": profit_amount / total_price * 100}
|
700 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
701 |
|
702 |
cost_df = pd.DataFrame(cost_distribution)
|
703 |
|
modules/resources/resources_app.py
CHANGED
@@ -461,21 +461,17 @@ class ResourcesApp:
|
|
461 |
# تحويل البيانات إلى DataFrame
|
462 |
price_history_df = pd.DataFrame(price_history_data)
|
463 |
|
464 |
-
#
|
465 |
-
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
475 |
-
labels={'التاريخ': 'التاريخ', 'السعر': 'السعر (ريال)', 'المادة': 'المادة'}
|
476 |
-
)
|
477 |
-
# عرض المخطط فقط إذا تم إنشاؤه
|
478 |
-
st.plotly_chart(fig, use_container_width=True)
|
479 |
|
480 |
def _render_materials_tab(self):
|
481 |
"""عرض تبويب المواد"""
|
@@ -1053,30 +1049,15 @@ class ResourcesApp:
|
|
1053 |
material_ids = {material['name']: material['id'] for material in st.session_state.materials}
|
1054 |
selected_ids = [material_ids[name] for name in selected_materials if name in material_ids]
|
1055 |
|
1056 |
-
# التحقق من وجود بيانات سعرية في session_state.price_history
|
1057 |
-
if 'price_history' not in st.session_state or not st.session_state.price_history:
|
1058 |
-
st.warning("لا توجد بيانات أسعار متاحة للتحليل.")
|
1059 |
-
return
|
1060 |
-
|
1061 |
price_history_data = []
|
1062 |
for entry in st.session_state.price_history:
|
1063 |
if entry['material_id'] in selected_ids:
|
1064 |
-
# الحصول على اسم المادة من المعرف
|
1065 |
material_name = next((material['name'] for material in st.session_state.materials if material['id'] == entry['material_id']), "")
|
1066 |
-
|
1067 |
-
|
1068 |
-
|
1069 |
-
|
1070 |
-
|
1071 |
-
price_history_data.append({
|
1072 |
-
'material': material_name, # استخدام أسماء إنجليزية للمفاتيح
|
1073 |
-
'date': pd.to_datetime(entry['date']),
|
1074 |
-
'price': float(entry['price']) # التأكد من تحويل السعر إلى رقم
|
1075 |
-
})
|
1076 |
-
except (ValueError, TypeError) as e:
|
1077 |
-
# تسجيل أخطاء تحويل البيانات
|
1078 |
-
st.error(f"خطأ في معالجة البيانات: {e}")
|
1079 |
-
continue
|
1080 |
|
1081 |
if not price_history_data:
|
1082 |
st.warning("لا توجد بيانات أسعار متاحة للمواد المختارة.")
|
@@ -1085,37 +1066,32 @@ class ResourcesApp:
|
|
1085 |
# تحويل البيانات إلى DataFrame
|
1086 |
price_history_df = pd.DataFrame(price_history_data)
|
1087 |
|
1088 |
-
#
|
1089 |
-
|
1090 |
-
|
1091 |
-
|
1092 |
-
|
1093 |
-
|
1094 |
-
|
1095 |
-
|
1096 |
-
|
1097 |
-
|
1098 |
-
|
1099 |
-
labels={'date': 'التاريخ', 'price': 'السعر (ريال)', 'material': 'المادة'}
|
1100 |
-
)
|
1101 |
-
|
1102 |
-
st.plotly_chart(fig, use_container_width=True)
|
1103 |
|
1104 |
# حساب التغيرات في الأسعار
|
1105 |
materials_price_changes = []
|
1106 |
|
1107 |
for material_name in selected_materials:
|
1108 |
-
|
1109 |
-
material_prices = price_history_df[price_history_df['material'] == material_name].sort_values('date')
|
1110 |
|
1111 |
if len(material_prices) >= 2:
|
1112 |
-
first_price = material_prices.iloc[0]['
|
1113 |
-
last_price = material_prices.iloc[-1]['
|
1114 |
price_change = last_price - first_price
|
1115 |
price_change_percent = (price_change / first_price) * 100
|
1116 |
|
1117 |
# حساب التقلب (الانحراف المعياري)
|
1118 |
-
price_volatility = material_prices['
|
1119 |
|
1120 |
materials_price_changes.append({
|
1121 |
'المادة': material_name,
|
@@ -1283,21 +1259,17 @@ class ResourcesApp:
|
|
1283 |
price_history_data = []
|
1284 |
for entry in st.session_state.price_history:
|
1285 |
if entry['material_id'] == material_id:
|
1286 |
-
|
1287 |
-
|
1288 |
-
|
1289 |
-
|
1290 |
-
})
|
1291 |
-
except (ValueError, TypeError) as e:
|
1292 |
-
st.error(f"خطأ في معالجة البيانات: {e}")
|
1293 |
-
continue
|
1294 |
|
1295 |
if not price_history_data:
|
1296 |
st.warning("لا توجد بيانات تاريخية كافية للمادة المحددة للقيام بالتوقع.")
|
1297 |
return
|
1298 |
|
1299 |
# تحويل البيانات إلى DataFrame
|
1300 |
-
price_history_df = pd.DataFrame(price_history_data).sort_values('
|
1301 |
|
1302 |
# إجراء التوقع
|
1303 |
# في الواقع، ستستخدم نماذج تعلم آلي مثل ARIMA أو Prophet
|
@@ -1306,7 +1278,7 @@ class ResourcesApp:
|
|
1306 |
# حساب متوسط التغير الشهري
|
1307 |
monthly_changes = []
|
1308 |
for i in range(1, len(price_history_df)):
|
1309 |
-
monthly_changes.append(price_history_df.iloc[i]['
|
1310 |
|
1311 |
if monthly_changes:
|
1312 |
avg_monthly_change = sum(monthly_changes) / len(monthly_changes)
|
@@ -1314,8 +1286,8 @@ class ResourcesApp:
|
|
1314 |
avg_monthly_change = 0
|
1315 |
|
1316 |
# إنشاء بيانات التوقع
|
1317 |
-
last_date = price_history_df['
|
1318 |
-
last_price = price_history_df.loc[price_history_df['
|
1319 |
|
1320 |
forecast_dates = pd.date_range(start=last_date + pd.DateOffset(months=1), periods=forecast_period, freq='M')
|
1321 |
forecast_prices = [last_price + (i+1) * avg_monthly_change for i in range(forecast_period)]
|
@@ -1324,25 +1296,25 @@ class ResourcesApp:
|
|
1324 |
forecast_prices = [price + random.uniform(-price*0.05, price*0.05) for price in forecast_prices]
|
1325 |
|
1326 |
forecast_df = pd.DataFrame({
|
1327 |
-
'
|
1328 |
-
'
|
1329 |
-
'
|
1330 |
})
|
1331 |
|
1332 |
# دمج البيانات التاريخية والتوقع
|
1333 |
historical_df = price_history_df.copy()
|
1334 |
-
historical_df['
|
1335 |
|
1336 |
combined_df = pd.concat([historical_df, forecast_df], ignore_index=True)
|
1337 |
|
1338 |
# عرض المخطط
|
1339 |
fig = px.line(
|
1340 |
combined_df,
|
1341 |
-
x='
|
1342 |
-
y='
|
1343 |
-
color='
|
1344 |
title=f'توقع أسعار {selected_material} للـ {forecast_period} أشهر القادمة',
|
1345 |
-
labels={'
|
1346 |
color_discrete_map={'تاريخي': 'blue', 'توقع': 'red'}
|
1347 |
)
|
1348 |
|
@@ -1377,14 +1349,9 @@ class ResourcesApp:
|
|
1377 |
st.markdown("#### جدول توقع الأسعار")
|
1378 |
|
1379 |
forecast_table = forecast_df.copy()
|
1380 |
-
forecast_table['
|
1381 |
-
forecast_table['
|
1382 |
-
|
1383 |
-
forecast_table = forecast_table.rename(columns={
|
1384 |
-
'date': 'التاريخ',
|
1385 |
-
'price': 'السعر'
|
1386 |
-
})
|
1387 |
-
forecast_table = forecast_table.drop(columns=['type'])
|
1388 |
|
1389 |
st.dataframe(forecast_table, use_container_width=True, hide_index=True)
|
1390 |
|
|
|
461 |
# تحويل البيانات إلى DataFrame
|
462 |
price_history_df = pd.DataFrame(price_history_data)
|
463 |
|
464 |
+
# رسم المخطط الخطي
|
465 |
+
fig = px.line(
|
466 |
+
price_history_df,
|
467 |
+
x='التاريخ',
|
468 |
+
y='السعر',
|
469 |
+
color='المادة',
|
470 |
+
title='تطور أسعار المواد الرئيسية خلال العام الماضي',
|
471 |
+
labels={'price': 'السعر (ريال)', 'date': 'التاريخ'}
|
472 |
+
)
|
473 |
+
|
474 |
+
st.plotly_chart(fig, use_container_width=True)
|
|
|
|
|
|
|
|
|
475 |
|
476 |
def _render_materials_tab(self):
|
477 |
"""عرض تبويب المواد"""
|
|
|
1049 |
material_ids = {material['name']: material['id'] for material in st.session_state.materials}
|
1050 |
selected_ids = [material_ids[name] for name in selected_materials if name in material_ids]
|
1051 |
|
|
|
|
|
|
|
|
|
|
|
1052 |
price_history_data = []
|
1053 |
for entry in st.session_state.price_history:
|
1054 |
if entry['material_id'] in selected_ids:
|
|
|
1055 |
material_name = next((material['name'] for material in st.session_state.materials if material['id'] == entry['material_id']), "")
|
1056 |
+
price_history_data.append({
|
1057 |
+
'المادة': material_name,
|
1058 |
+
'التاريخ': pd.to_datetime(entry['date']),
|
1059 |
+
'السعر': entry['price']
|
1060 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1061 |
|
1062 |
if not price_history_data:
|
1063 |
st.warning("لا توجد بيانات أسعار متاحة للمواد المختارة.")
|
|
|
1066 |
# تحويل البيانات إلى DataFrame
|
1067 |
price_history_df = pd.DataFrame(price_history_data)
|
1068 |
|
1069 |
+
# عرض المخطط الخطي للأسعار
|
1070 |
+
fig = px.line(
|
1071 |
+
price_history_df,
|
1072 |
+
x='التاريخ',
|
1073 |
+
y='السعر',
|
1074 |
+
color='المادة',
|
1075 |
+
title='تطور أسعار المواد المختارة',
|
1076 |
+
labels={'السعر': 'السعر (ريال)'}
|
1077 |
+
)
|
1078 |
+
|
1079 |
+
st.plotly_chart(fig, use_container_width=True)
|
|
|
|
|
|
|
|
|
1080 |
|
1081 |
# حساب التغيرات في الأسعار
|
1082 |
materials_price_changes = []
|
1083 |
|
1084 |
for material_name in selected_materials:
|
1085 |
+
material_prices = price_history_df[price_history_df['المادة'] == material_name].sort_values('التاريخ')
|
|
|
1086 |
|
1087 |
if len(material_prices) >= 2:
|
1088 |
+
first_price = material_prices.iloc[0]['السعر']
|
1089 |
+
last_price = material_prices.iloc[-1]['السعر']
|
1090 |
price_change = last_price - first_price
|
1091 |
price_change_percent = (price_change / first_price) * 100
|
1092 |
|
1093 |
# حساب التقلب (الانحراف المعياري)
|
1094 |
+
price_volatility = material_prices['السعر'].std()
|
1095 |
|
1096 |
materials_price_changes.append({
|
1097 |
'المادة': material_name,
|
|
|
1259 |
price_history_data = []
|
1260 |
for entry in st.session_state.price_history:
|
1261 |
if entry['material_id'] == material_id:
|
1262 |
+
price_history_data.append({
|
1263 |
+
'التاريخ': pd.to_datetime(entry['date']),
|
1264 |
+
'السعر': entry['price']
|
1265 |
+
})
|
|
|
|
|
|
|
|
|
1266 |
|
1267 |
if not price_history_data:
|
1268 |
st.warning("لا توجد بيانات تاريخية كافية للمادة المحددة للقيام بالتوقع.")
|
1269 |
return
|
1270 |
|
1271 |
# تحويل البيانات إلى DataFrame
|
1272 |
+
price_history_df = pd.DataFrame(price_history_data).sort_values('التاريخ')
|
1273 |
|
1274 |
# إجراء التوقع
|
1275 |
# في الواقع، ستستخدم نماذج تعلم آلي مثل ARIMA أو Prophet
|
|
|
1278 |
# حساب متوسط التغير الشهري
|
1279 |
monthly_changes = []
|
1280 |
for i in range(1, len(price_history_df)):
|
1281 |
+
monthly_changes.append(price_history_df.iloc[i]['السعر'] - price_history_df.iloc[i-1]['السعر'])
|
1282 |
|
1283 |
if monthly_changes:
|
1284 |
avg_monthly_change = sum(monthly_changes) / len(monthly_changes)
|
|
|
1286 |
avg_monthly_change = 0
|
1287 |
|
1288 |
# إنشاء بيانات التوقع
|
1289 |
+
last_date = price_history_df['التاريخ'].max()
|
1290 |
+
last_price = price_history_df.loc[price_history_df['التاريخ'] == last_date, 'السعر'].values[0]
|
1291 |
|
1292 |
forecast_dates = pd.date_range(start=last_date + pd.DateOffset(months=1), periods=forecast_period, freq='M')
|
1293 |
forecast_prices = [last_price + (i+1) * avg_monthly_change for i in range(forecast_period)]
|
|
|
1296 |
forecast_prices = [price + random.uniform(-price*0.05, price*0.05) for price in forecast_prices]
|
1297 |
|
1298 |
forecast_df = pd.DataFrame({
|
1299 |
+
'التاريخ': forecast_dates,
|
1300 |
+
'السعر': forecast_prices,
|
1301 |
+
'النوع': ['توقع'] * forecast_period
|
1302 |
})
|
1303 |
|
1304 |
# دمج البيانات التاريخية والتوقع
|
1305 |
historical_df = price_history_df.copy()
|
1306 |
+
historical_df['النوع'] = ['تاريخي'] * len(historical_df)
|
1307 |
|
1308 |
combined_df = pd.concat([historical_df, forecast_df], ignore_index=True)
|
1309 |
|
1310 |
# عرض المخطط
|
1311 |
fig = px.line(
|
1312 |
combined_df,
|
1313 |
+
x='التاريخ',
|
1314 |
+
y='السعر',
|
1315 |
+
color='النوع',
|
1316 |
title=f'توقع أسعار {selected_material} للـ {forecast_period} أشهر القادمة',
|
1317 |
+
labels={'السعر': 'السعر (ريال)'},
|
1318 |
color_discrete_map={'تاريخي': 'blue', 'توقع': 'red'}
|
1319 |
)
|
1320 |
|
|
|
1349 |
st.markdown("#### جدول توقع الأسعار")
|
1350 |
|
1351 |
forecast_table = forecast_df.copy()
|
1352 |
+
forecast_table['التاريخ'] = forecast_table['التاريخ'].dt.strftime('%Y-%m')
|
1353 |
+
forecast_table['السعر'] = forecast_table['السعر'].apply(lambda x: f"{x:,.2f} ريال")
|
1354 |
+
forecast_table = forecast_table.drop(columns=['النوع'])
|
|
|
|
|
|
|
|
|
|
|
1355 |
|
1356 |
st.dataframe(forecast_table, use_container_width=True, hide_index=True)
|
1357 |
|
static/css/backup/enhanced-styles.css
ADDED
@@ -0,0 +1,913 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* أنماط CSS المحسنة للنظام - تم التحديث 2025 */
|
2 |
+
@import url('https://fonts.googleapis.com/css2?family=Almarai:wght@300;400;700;800&family=Cairo:wght@200;300;400;500;600;700;800;900&family=Tajawal:wght@200;300;400;500;700;800;900&display=swap');
|
3 |
+
|
4 |
+
:root {
|
5 |
+
--primary-color: #0EA5A5; /* لون رئيسي جديد: فيروزي */
|
6 |
+
--primary-light: rgba(14, 165, 165, 0.1);
|
7 |
+
--primary-dark: #088585;
|
8 |
+
--primary-gradient: linear-gradient(135deg, #0EA5A5, #088585);
|
9 |
+
--secondary-color: #FF9A3C; /* لون ثانوي: برتقالي */
|
10 |
+
--secondary-light: rgba(255, 154, 60, 0.1);
|
11 |
+
--text-dark: #1d2b36;
|
12 |
+
--text-medium: #3a4f5f;
|
13 |
+
--text-light: #607d94;
|
14 |
+
--background-light: #f8f9fa;
|
15 |
+
--border-color: #e1e5ea;
|
16 |
+
--danger-color: #e3342f;
|
17 |
+
--success-color: #38c172;
|
18 |
+
--warning-color: #f7b731;
|
19 |
+
--info-color: #3490dc;
|
20 |
+
--card-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
21 |
+
--header-gradient: linear-gradient(120deg, #0EA5A5, #088585);
|
22 |
+
--sidebar-gradient: linear-gradient(180deg, #1d2b36, #2d3a45);
|
23 |
+
--border-radius: 10px;
|
24 |
+
--transition-speed: 0.3s;
|
25 |
+
}
|
26 |
+
|
27 |
+
/* تعيين اتجاه النص من اليمين إلى اليسار للغة العربية */
|
28 |
+
body {
|
29 |
+
direction: rtl;
|
30 |
+
text-align: right;
|
31 |
+
font-family: 'Almarai', 'Tajawal', 'Cairo', sans-serif;
|
32 |
+
color: var(--text-dark);
|
33 |
+
background-color: #fafafa;
|
34 |
+
/* التوافق مع جميع المتصفحات والأجهزة */
|
35 |
+
-webkit-font-smoothing: antialiased;
|
36 |
+
-moz-osx-font-smoothing: grayscale;
|
37 |
+
text-rendering: optimizeLegibility;
|
38 |
+
}
|
39 |
+
|
40 |
+
/* أنماط العناوين */
|
41 |
+
h1, h2, h3, h4, h5, h6 {
|
42 |
+
font-family: 'Almarai', 'Tajawal', 'Cairo', sans-serif;
|
43 |
+
color: var(--text-dark);
|
44 |
+
font-weight: 700;
|
45 |
+
line-height: 1.4;
|
46 |
+
margin-bottom: 0.75rem;
|
47 |
+
}
|
48 |
+
|
49 |
+
/* أنماط العنوان الرئيسي بتدرج لوني */
|
50 |
+
.main-title {
|
51 |
+
background: var(--primary-gradient);
|
52 |
+
-webkit-background-clip: text;
|
53 |
+
background-clip: text;
|
54 |
+
color: transparent;
|
55 |
+
font-size: 2.25rem;
|
56 |
+
font-weight: 800;
|
57 |
+
text-align: center;
|
58 |
+
margin: 1.5rem 0;
|
59 |
+
padding: 0.5rem;
|
60 |
+
position: relative;
|
61 |
+
}
|
62 |
+
|
63 |
+
.main-title::after {
|
64 |
+
content: "";
|
65 |
+
position: absolute;
|
66 |
+
bottom: -5px;
|
67 |
+
left: 30%;
|
68 |
+
right: 30%;
|
69 |
+
height: 3px;
|
70 |
+
background: var(--primary-gradient);
|
71 |
+
border-radius: 3px;
|
72 |
+
}
|
73 |
+
|
74 |
+
/* أنماط ترويسة الصفحة محسنة */
|
75 |
+
.header-container {
|
76 |
+
display: flex;
|
77 |
+
flex-direction: row;
|
78 |
+
justify-content: space-between;
|
79 |
+
align-items: center;
|
80 |
+
padding: 1rem 1.5rem;
|
81 |
+
background: var(--header-gradient);
|
82 |
+
border-radius: var(--border-radius);
|
83 |
+
margin-bottom: 1.5rem;
|
84 |
+
box-shadow: var(--card-shadow);
|
85 |
+
color: white;
|
86 |
+
}
|
87 |
+
|
88 |
+
.header-title {
|
89 |
+
margin-right: 1.25rem;
|
90 |
+
}
|
91 |
+
|
92 |
+
.header-title h1 {
|
93 |
+
margin: 0;
|
94 |
+
font-size: 1.75rem;
|
95 |
+
color: white;
|
96 |
+
font-weight: 800;
|
97 |
+
}
|
98 |
+
|
99 |
+
.header-title p {
|
100 |
+
margin: 0.25rem 0 0 0;
|
101 |
+
font-size: 0.9rem;
|
102 |
+
color: rgba(255, 255, 255, 0.8);
|
103 |
+
font-weight: 400;
|
104 |
+
}
|
105 |
+
|
106 |
+
.header-info {
|
107 |
+
display: flex;
|
108 |
+
align-items: center;
|
109 |
+
}
|
110 |
+
|
111 |
+
.date-box {
|
112 |
+
display: flex;
|
113 |
+
background-color: rgba(255, 255, 255, 0.2);
|
114 |
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
115 |
+
color: white;
|
116 |
+
border-radius: 8px;
|
117 |
+
padding: 0.5rem 0.75rem;
|
118 |
+
margin-left: 1rem;
|
119 |
+
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
120 |
+
backdrop-filter: blur(5px);
|
121 |
+
}
|
122 |
+
|
123 |
+
.date-day {
|
124 |
+
font-size: 1.75rem;
|
125 |
+
font-weight: bold;
|
126 |
+
margin-left: 0.5rem;
|
127 |
+
line-height: 1;
|
128 |
+
}
|
129 |
+
|
130 |
+
.date-info {
|
131 |
+
display: flex;
|
132 |
+
flex-direction: column;
|
133 |
+
font-size: 0.8rem;
|
134 |
+
}
|
135 |
+
|
136 |
+
.date-month {
|
137 |
+
font-weight: bold;
|
138 |
+
line-height: 1.2;
|
139 |
+
}
|
140 |
+
|
141 |
+
.date-year {
|
142 |
+
line-height: 1;
|
143 |
+
}
|
144 |
+
|
145 |
+
/* أنماط قائمة التنقل الجديدة */
|
146 |
+
.nav-menu {
|
147 |
+
background-color: white;
|
148 |
+
border-radius: var(--border-radius);
|
149 |
+
padding: 0.5rem;
|
150 |
+
box-shadow: var(--card-shadow);
|
151 |
+
margin: 1rem 0;
|
152 |
+
}
|
153 |
+
|
154 |
+
.nav-menu ul {
|
155 |
+
display: flex;
|
156 |
+
list-style: none;
|
157 |
+
padding: 0;
|
158 |
+
margin: 0;
|
159 |
+
justify-content: flex-end;
|
160 |
+
flex-wrap: wrap;
|
161 |
+
}
|
162 |
+
|
163 |
+
.nav-menu li {
|
164 |
+
margin: 0.25rem;
|
165 |
+
}
|
166 |
+
|
167 |
+
.nav-menu a {
|
168 |
+
display: flex;
|
169 |
+
align-items: center;
|
170 |
+
color: var(--text-medium);
|
171 |
+
text-decoration: none;
|
172 |
+
padding: 0.5rem 0.75rem;
|
173 |
+
border-radius: 6px;
|
174 |
+
transition: all var(--transition-speed);
|
175 |
+
font-weight: 500;
|
176 |
+
border: 1px solid transparent;
|
177 |
+
}
|
178 |
+
|
179 |
+
.nav-menu a:hover {
|
180 |
+
background-color: var(--primary-light);
|
181 |
+
color: var(--primary-color);
|
182 |
+
border-color: var(--primary-color);
|
183 |
+
transform: translateY(-2px);
|
184 |
+
}
|
185 |
+
|
186 |
+
.nav-icon {
|
187 |
+
margin-left: 0.5rem;
|
188 |
+
font-size: 1.25rem;
|
189 |
+
}
|
190 |
+
|
191 |
+
/* أنماط عنوان الوحدة */
|
192 |
+
.module-title {
|
193 |
+
color: var(--text-dark);
|
194 |
+
font-size: 1.75rem;
|
195 |
+
margin-bottom: 1.25rem;
|
196 |
+
border-right: 4px solid var(--primary-color);
|
197 |
+
padding-right: 0.75rem;
|
198 |
+
position: relative;
|
199 |
+
}
|
200 |
+
|
201 |
+
/* أنماط بطاقات المعلومات المحسنة */
|
202 |
+
.info-card {
|
203 |
+
background-color: white;
|
204 |
+
border-radius: var(--border-radius);
|
205 |
+
padding: 1.5rem;
|
206 |
+
margin-bottom: 1.25rem;
|
207 |
+
box-shadow: var(--card-shadow);
|
208 |
+
border-top: 4px solid var(--primary-color);
|
209 |
+
transition: transform var(--transition-speed);
|
210 |
+
}
|
211 |
+
|
212 |
+
.info-card:hover {
|
213 |
+
transform: translateY(-5px);
|
214 |
+
}
|
215 |
+
|
216 |
+
.info-card h3 {
|
217 |
+
color: var(--text-dark);
|
218 |
+
margin-top: 0;
|
219 |
+
margin-bottom: 0.75rem;
|
220 |
+
font-weight: 700;
|
221 |
+
}
|
222 |
+
|
223 |
+
.info-card p {
|
224 |
+
color: var(--text-medium);
|
225 |
+
margin: 0;
|
226 |
+
line-height: 1.6;
|
227 |
+
}
|
228 |
+
|
229 |
+
/* أنماط الجداول */
|
230 |
+
.dataframe {
|
231 |
+
width: 100%;
|
232 |
+
border-collapse: separate;
|
233 |
+
border-spacing: 0;
|
234 |
+
margin-bottom: 1.5rem;
|
235 |
+
border-radius: var(--border-radius);
|
236 |
+
overflow: hidden;
|
237 |
+
box-shadow: var(--card-shadow);
|
238 |
+
}
|
239 |
+
|
240 |
+
.dataframe th {
|
241 |
+
background-color: var(--primary-color);
|
242 |
+
color: white;
|
243 |
+
text-align: right;
|
244 |
+
padding: 0.75rem 1rem;
|
245 |
+
font-weight: 600;
|
246 |
+
border: none;
|
247 |
+
}
|
248 |
+
|
249 |
+
.dataframe td {
|
250 |
+
padding: 0.75rem 1rem;
|
251 |
+
border-bottom: 1px solid var(--border-color);
|
252 |
+
text-align: right;
|
253 |
+
background-color: white;
|
254 |
+
}
|
255 |
+
|
256 |
+
.dataframe tr:last-child td {
|
257 |
+
border-bottom: none;
|
258 |
+
}
|
259 |
+
|
260 |
+
.dataframe tr:nth-child(even) td {
|
261 |
+
background-color: rgba(248, 249, 250, 0.7);
|
262 |
+
}
|
263 |
+
|
264 |
+
.dataframe tr:hover td {
|
265 |
+
background-color: var(--primary-light);
|
266 |
+
}
|
267 |
+
|
268 |
+
/* أنماط الأزرار الجديدة */
|
269 |
+
button, .stButton>button {
|
270 |
+
background: var(--primary-gradient);
|
271 |
+
color: white;
|
272 |
+
border: none;
|
273 |
+
border-radius: 6px;
|
274 |
+
padding: 0.6rem 1.25rem;
|
275 |
+
cursor: pointer;
|
276 |
+
transition: all var(--transition-speed);
|
277 |
+
font-weight: 600;
|
278 |
+
font-size: 0.95rem;
|
279 |
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
280 |
+
display: inline-flex;
|
281 |
+
align-items: center;
|
282 |
+
justify-content: center;
|
283 |
+
}
|
284 |
+
|
285 |
+
button:hover, .stButton>button:hover {
|
286 |
+
background: var(--primary-dark);
|
287 |
+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
|
288 |
+
transform: translateY(-2px);
|
289 |
+
}
|
290 |
+
|
291 |
+
button:active, .stButton>button:active {
|
292 |
+
transform: translateY(0);
|
293 |
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
294 |
+
}
|
295 |
+
|
296 |
+
/* أزرار ثانوية */
|
297 |
+
.btn-secondary, .stButton.secondary>button {
|
298 |
+
background: white;
|
299 |
+
color: var(--primary-color);
|
300 |
+
border: 1px solid var(--primary-color);
|
301 |
+
}
|
302 |
+
|
303 |
+
.btn-secondary:hover, .stButton.secondary>button:hover {
|
304 |
+
background: var(--primary-light);
|
305 |
+
}
|
306 |
+
|
307 |
+
/* أنماط المخططات */
|
308 |
+
.plot-container {
|
309 |
+
background-color: white;
|
310 |
+
border-radius: var(--border-radius);
|
311 |
+
padding: 1rem;
|
312 |
+
margin: 1.25rem 0;
|
313 |
+
box-shadow: var(--card-shadow);
|
314 |
+
}
|
315 |
+
|
316 |
+
/* أنماط بطاقات الابتكارات */
|
317 |
+
.innovation-card {
|
318 |
+
background-color: white;
|
319 |
+
border-radius: var(--border-radius);
|
320 |
+
padding: 1.5rem;
|
321 |
+
margin-bottom: 1.25rem;
|
322 |
+
border-right: 4px solid var(--primary-color);
|
323 |
+
box-shadow: var(--card-shadow);
|
324 |
+
transition: transform var(--transition-speed);
|
325 |
+
}
|
326 |
+
|
327 |
+
.innovation-card:hover {
|
328 |
+
transform: translateY(-5px);
|
329 |
+
}
|
330 |
+
|
331 |
+
.innovation-icon {
|
332 |
+
font-size: 2rem;
|
333 |
+
margin-bottom: 0.75rem;
|
334 |
+
background: var(--primary-gradient);
|
335 |
+
-webkit-background-clip: text;
|
336 |
+
background-clip: text;
|
337 |
+
color: transparent;
|
338 |
+
}
|
339 |
+
|
340 |
+
.innovation-card h3 {
|
341 |
+
color: var(--text-dark);
|
342 |
+
margin-bottom: 0.75rem;
|
343 |
+
font-weight: 700;
|
344 |
+
}
|
345 |
+
|
346 |
+
.innovation-card p {
|
347 |
+
color: var(--text-medium);
|
348 |
+
font-size: 0.95rem;
|
349 |
+
line-height: 1.6;
|
350 |
+
}
|
351 |
+
|
352 |
+
/* أنماط فريق التطوير */
|
353 |
+
.team-section {
|
354 |
+
display: flex;
|
355 |
+
flex-wrap: wrap;
|
356 |
+
justify-content: center;
|
357 |
+
gap: 1.5rem;
|
358 |
+
margin: 2rem 0;
|
359 |
+
}
|
360 |
+
|
361 |
+
.team-member {
|
362 |
+
text-align: center;
|
363 |
+
margin-bottom: 1.25rem;
|
364 |
+
background-color: white;
|
365 |
+
border-radius: var(--border-radius);
|
366 |
+
padding: 1.5rem;
|
367 |
+
box-shadow: var(--card-shadow);
|
368 |
+
transition: transform var(--transition-speed);
|
369 |
+
width: 230px;
|
370 |
+
}
|
371 |
+
|
372 |
+
.team-member:hover {
|
373 |
+
transform: translateY(-5px);
|
374 |
+
}
|
375 |
+
|
376 |
+
.team-member h3 {
|
377 |
+
color: var(--text-dark);
|
378 |
+
margin-bottom: 0.3rem;
|
379 |
+
font-size: 1.1rem;
|
380 |
+
font-weight: 700;
|
381 |
+
}
|
382 |
+
|
383 |
+
.team-member h4 {
|
384 |
+
color: var(--primary-color);
|
385 |
+
margin-top: 0;
|
386 |
+
margin-bottom: 0.75rem;
|
387 |
+
font-size: 0.9rem;
|
388 |
+
}
|
389 |
+
|
390 |
+
.team-member p {
|
391 |
+
color: var(--text-medium);
|
392 |
+
font-size: 0.85rem;
|
393 |
+
line-height: 1.5;
|
394 |
+
}
|
395 |
+
|
396 |
+
.avatar {
|
397 |
+
background: var(--primary-gradient);
|
398 |
+
color: white;
|
399 |
+
width: 90px;
|
400 |
+
height: 90px;
|
401 |
+
border-radius: 50%;
|
402 |
+
display: flex;
|
403 |
+
justify-content: center;
|
404 |
+
align-items: center;
|
405 |
+
margin: 0 auto 1rem auto;
|
406 |
+
font-size: 2rem;
|
407 |
+
font-weight: 700;
|
408 |
+
box-shadow: 0 5px 15px rgba(14, 165, 165, 0.3);
|
409 |
+
}
|
410 |
+
|
411 |
+
/* أنماط تذييل الصفحة */
|
412 |
+
.footer {
|
413 |
+
text-align: center;
|
414 |
+
color: var(--text-light);
|
415 |
+
font-size: 0.85rem;
|
416 |
+
margin-top: 2rem;
|
417 |
+
margin-bottom: 1rem;
|
418 |
+
padding: 1rem;
|
419 |
+
border-top: 1px solid var(--border-color);
|
420 |
+
}
|
421 |
+
|
422 |
+
/* أنماط رسائل التنبيه المحسنة */
|
423 |
+
.alert {
|
424 |
+
padding: 1rem 1.25rem;
|
425 |
+
border-radius: var(--border-radius);
|
426 |
+
margin-bottom: 1rem;
|
427 |
+
position: relative;
|
428 |
+
border-right: 4px solid;
|
429 |
+
}
|
430 |
+
|
431 |
+
.alert-icon {
|
432 |
+
margin-left: 0.5rem;
|
433 |
+
font-size: 1.25rem;
|
434 |
+
}
|
435 |
+
|
436 |
+
.alert-info {
|
437 |
+
background-color: rgba(52, 144, 220, 0.1);
|
438 |
+
color: var(--info-color);
|
439 |
+
border-right-color: var(--info-color);
|
440 |
+
}
|
441 |
+
|
442 |
+
.alert-success {
|
443 |
+
background-color: rgba(56, 193, 114, 0.1);
|
444 |
+
color: var(--success-color);
|
445 |
+
border-right-color: var(--success-color);
|
446 |
+
}
|
447 |
+
|
448 |
+
.alert-warning {
|
449 |
+
background-color: rgba(247, 183, 49, 0.1);
|
450 |
+
color: var(--warning-color);
|
451 |
+
border-right-color: var(--warning-color);
|
452 |
+
}
|
453 |
+
|
454 |
+
.alert-danger {
|
455 |
+
background-color: rgba(227, 52, 47, 0.1);
|
456 |
+
color: var(--danger-color);
|
457 |
+
border-right-color: var(--danger-color);
|
458 |
+
}
|
459 |
+
|
460 |
+
/* أنماط التبويبات المحسنة */
|
461 |
+
.stTabs [data-baseweb="tab-list"] {
|
462 |
+
gap: 1px;
|
463 |
+
background-color: var(--background-light);
|
464 |
+
border-radius: var(--border-radius);
|
465 |
+
padding: 0.3rem;
|
466 |
+
}
|
467 |
+
|
468 |
+
.stTabs [data-baseweb="tab"] {
|
469 |
+
height: 50px;
|
470 |
+
white-space: pre-wrap;
|
471 |
+
background-color: white;
|
472 |
+
border-radius: 6px;
|
473 |
+
gap: 1px;
|
474 |
+
padding: 0.6rem 1rem;
|
475 |
+
font-family: 'Almarai', 'Tajawal', sans-serif;
|
476 |
+
font-weight: 500;
|
477 |
+
transition: all var(--transition-speed);
|
478 |
+
}
|
479 |
+
|
480 |
+
.stTabs [aria-selected="true"] {
|
481 |
+
background: var(--primary-gradient) !important;
|
482 |
+
color: white !important;
|
483 |
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
484 |
+
}
|
485 |
+
|
486 |
+
/* أنماط مخصصة لدعم اللغة العربية في إدخالات النصوص والأرقام */
|
487 |
+
input, textarea, .stTextInput>div>div>input, .stNumberInput>div>div>input {
|
488 |
+
direction: rtl;
|
489 |
+
text-align: right;
|
490 |
+
font-family: 'Almarai', 'Tajawal', sans-serif;
|
491 |
+
font-size: 0.95rem;
|
492 |
+
padding: 0.6rem 0.75rem;
|
493 |
+
border-radius: 6px;
|
494 |
+
border: 1px solid var(--border-color);
|
495 |
+
transition: border-color var(--transition-speed);
|
496 |
+
}
|
497 |
+
|
498 |
+
input:focus, textarea:focus, .stTextInput>div>div>input:focus, .stNumberInput>div>div>input:focus {
|
499 |
+
outline: none;
|
500 |
+
border-color: var(--primary-color);
|
501 |
+
box-shadow: 0 0 0 1px var(--primary-light);
|
502 |
+
}
|
503 |
+
|
504 |
+
/* أنماط قائمة الخيارات */
|
505 |
+
.stSelectbox [data-baseweb="select"] {
|
506 |
+
direction: rtl;
|
507 |
+
text-align: right;
|
508 |
+
font-family: 'Almarai', 'Tajawal', sans-serif;
|
509 |
+
}
|
510 |
+
|
511 |
+
/* أنماط للنسخة المحمولة والاستجابة للشاشات */
|
512 |
+
@media (max-width: 992px) {
|
513 |
+
.main-title {
|
514 |
+
font-size: 1.75rem;
|
515 |
+
}
|
516 |
+
|
517 |
+
.header-container {
|
518 |
+
padding: 1rem;
|
519 |
+
}
|
520 |
+
|
521 |
+
.header-title h1 {
|
522 |
+
font-size: 1.5rem;
|
523 |
+
}
|
524 |
+
}
|
525 |
+
|
526 |
+
@media (max-width: 768px) {
|
527 |
+
.header-container {
|
528 |
+
flex-direction: column;
|
529 |
+
align-items: flex-start;
|
530 |
+
}
|
531 |
+
|
532 |
+
.header-info {
|
533 |
+
margin-top: 1rem;
|
534 |
+
width: 100%;
|
535 |
+
justify-content: flex-start;
|
536 |
+
}
|
537 |
+
|
538 |
+
.nav-menu ul {
|
539 |
+
flex-wrap: wrap;
|
540 |
+
justify-content: flex-start;
|
541 |
+
}
|
542 |
+
|
543 |
+
.nav-menu li {
|
544 |
+
margin-bottom: 0.5rem;
|
545 |
+
margin-left: 0.5rem;
|
546 |
+
width: calc(50% - 1rem);
|
547 |
+
}
|
548 |
+
|
549 |
+
.module-title {
|
550 |
+
font-size: 1.5rem;
|
551 |
+
}
|
552 |
+
|
553 |
+
.main-title {
|
554 |
+
font-size: 1.4rem;
|
555 |
+
}
|
556 |
+
|
557 |
+
.innovation-card, .info-card {
|
558 |
+
margin-bottom: 1rem;
|
559 |
+
padding: 1rem;
|
560 |
+
}
|
561 |
+
|
562 |
+
.dataframe {
|
563 |
+
display: block;
|
564 |
+
overflow-x: auto;
|
565 |
+
}
|
566 |
+
|
567 |
+
.team-member {
|
568 |
+
width: 100%;
|
569 |
+
}
|
570 |
+
}
|
571 |
+
|
572 |
+
@media (max-width: 480px) {
|
573 |
+
.main-title {
|
574 |
+
font-size: 1.25rem;
|
575 |
+
}
|
576 |
+
|
577 |
+
.header-title h1 {
|
578 |
+
font-size: 1.25rem;
|
579 |
+
}
|
580 |
+
|
581 |
+
.nav-menu li {
|
582 |
+
width: 100%;
|
583 |
+
margin-left: 0;
|
584 |
+
}
|
585 |
+
}
|
586 |
+
|
587 |
+
/* تحسينات خاصة للأيفون والأجهزة ذات الشاشات الصغيرة */
|
588 |
+
@media only screen and (max-width: 375px) {
|
589 |
+
.nav-menu {
|
590 |
+
margin: 0.5rem 0;
|
591 |
+
}
|
592 |
+
|
593 |
+
button, .stButton>button {
|
594 |
+
padding: 0.5rem 0.75rem;
|
595 |
+
font-size: 0.9rem;
|
596 |
+
}
|
597 |
+
|
598 |
+
.module-title, .info-card h3, .innovation-card h3 {
|
599 |
+
font-size: 1.1rem;
|
600 |
+
}
|
601 |
+
}
|
602 |
+
|
603 |
+
/* أضف كلاس للأيقونات */
|
604 |
+
.icon {
|
605 |
+
font-size: 1.1rem;
|
606 |
+
margin-left: 0.5rem;
|
607 |
+
}
|
608 |
+
|
609 |
+
/* أنماط للصور والخلفيات */
|
610 |
+
.bg-light {
|
611 |
+
background-color: var(--background-light);
|
612 |
+
}
|
613 |
+
|
614 |
+
.card {
|
615 |
+
background: white;
|
616 |
+
border-radius: var(--border-radius);
|
617 |
+
box-shadow: var(--card-shadow);
|
618 |
+
padding: 1.25rem;
|
619 |
+
margin-bottom: 1.25rem;
|
620 |
+
}
|
621 |
+
|
622 |
+
/* قسم معلومات النظام */
|
623 |
+
.about-system {
|
624 |
+
margin: 2rem 0;
|
625 |
+
background: white;
|
626 |
+
border-radius: var(--border-radius);
|
627 |
+
box-shadow: var(--card-shadow);
|
628 |
+
padding: 1.5rem;
|
629 |
+
}
|
630 |
+
|
631 |
+
.about-system h2 {
|
632 |
+
color: var(--primary-color);
|
633 |
+
margin-bottom: 1rem;
|
634 |
+
padding-bottom: 0.5rem;
|
635 |
+
border-bottom: 2px solid var(--primary-light);
|
636 |
+
}
|
637 |
+
|
638 |
+
.about-system p {
|
639 |
+
line-height: 1.6;
|
640 |
+
margin-bottom: 1rem;
|
641 |
+
}
|
642 |
+
|
643 |
+
.about-system ul {
|
644 |
+
padding-right: 1.5rem;
|
645 |
+
margin-bottom: 1rem;
|
646 |
+
}
|
647 |
+
|
648 |
+
.about-system li {
|
649 |
+
margin-bottom: 0.5rem;
|
650 |
+
line-height: 1.6;
|
651 |
+
}
|
652 |
+
|
653 |
+
/* أنماط مؤشر التقدم */
|
654 |
+
.progress {
|
655 |
+
height: 0.5rem;
|
656 |
+
overflow: hidden;
|
657 |
+
background-color: var(--background-light);
|
658 |
+
border-radius: 0.25rem;
|
659 |
+
margin: 0.5rem 0 1rem 0;
|
660 |
+
}
|
661 |
+
|
662 |
+
.progress-bar {
|
663 |
+
height: 100%;
|
664 |
+
border-radius: 0.25rem;
|
665 |
+
background: var(--primary-gradient);
|
666 |
+
}
|
667 |
+
|
668 |
+
/* المؤقت للمواعيد النهائية */
|
669 |
+
.countdown-timer {
|
670 |
+
display: flex;
|
671 |
+
justify-content: center;
|
672 |
+
gap: 1rem;
|
673 |
+
margin: 1.5rem 0;
|
674 |
+
}
|
675 |
+
|
676 |
+
.time-block {
|
677 |
+
background: white;
|
678 |
+
border-radius: var(--border-radius);
|
679 |
+
padding: 0.75rem 1rem;
|
680 |
+
text-align: center;
|
681 |
+
min-width: 80px;
|
682 |
+
box-shadow: var(--card-shadow);
|
683 |
+
}
|
684 |
+
|
685 |
+
.time-value {
|
686 |
+
font-size: 1.75rem;
|
687 |
+
font-weight: 700;
|
688 |
+
color: var(--primary-color);
|
689 |
+
line-height: 1;
|
690 |
+
}
|
691 |
+
|
692 |
+
.time-label {
|
693 |
+
font-size: 0.8rem;
|
694 |
+
color: var(--text-medium);
|
695 |
+
margin-top: 0.25rem;
|
696 |
+
}
|
697 |
+
|
698 |
+
/* إعدادات المستخدم */
|
699 |
+
.settings-form {
|
700 |
+
background: white;
|
701 |
+
border-radius: var(--border-radius);
|
702 |
+
padding: 1.5rem;
|
703 |
+
box-shadow: var(--card-shadow);
|
704 |
+
}
|
705 |
+
|
706 |
+
.settings-group {
|
707 |
+
margin-bottom: 1.5rem;
|
708 |
+
}
|
709 |
+
|
710 |
+
.settings-group h3 {
|
711 |
+
font-size: 1.2rem;
|
712 |
+
margin-bottom: 1rem;
|
713 |
+
padding-bottom: 0.5rem;
|
714 |
+
border-bottom: 1px solid var(--border-color);
|
715 |
+
}
|
716 |
+
|
717 |
+
.settings-item {
|
718 |
+
margin-bottom: 1rem;
|
719 |
+
}
|
720 |
+
|
721 |
+
.settings-item label {
|
722 |
+
display: block;
|
723 |
+
margin-bottom: 0.5rem;
|
724 |
+
font-weight: 500;
|
725 |
+
}
|
726 |
+
|
727 |
+
/* أنماط للتسعير المتقدم */
|
728 |
+
.pricing-grid {
|
729 |
+
display: grid;
|
730 |
+
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
731 |
+
gap: 1.25rem;
|
732 |
+
margin: 1.5rem 0;
|
733 |
+
}
|
734 |
+
|
735 |
+
.price-card {
|
736 |
+
background: white;
|
737 |
+
border-radius: var(--border-radius);
|
738 |
+
padding: 1.25rem;
|
739 |
+
box-shadow: var(--card-shadow);
|
740 |
+
display: flex;
|
741 |
+
flex-direction: column;
|
742 |
+
}
|
743 |
+
|
744 |
+
.price-header {
|
745 |
+
margin-bottom: 1rem;
|
746 |
+
padding-bottom: 0.5rem;
|
747 |
+
border-bottom: 1px solid var(--border-color);
|
748 |
+
}
|
749 |
+
|
750 |
+
.price-value {
|
751 |
+
font-size: 1.5rem;
|
752 |
+
font-weight: 700;
|
753 |
+
color: var(--primary-color);
|
754 |
+
margin: 0.5rem 0;
|
755 |
+
}
|
756 |
+
|
757 |
+
.price-details {
|
758 |
+
flex-grow: 1;
|
759 |
+
}
|
760 |
+
|
761 |
+
.price-details ul {
|
762 |
+
padding-right: 1.25rem;
|
763 |
+
margin-bottom: 1rem;
|
764 |
+
}
|
765 |
+
|
766 |
+
.price-details li {
|
767 |
+
margin-bottom: 0.5rem;
|
768 |
+
line-height: 1.5;
|
769 |
+
}
|
770 |
+
|
771 |
+
.price-footer {
|
772 |
+
margin-top: auto;
|
773 |
+
padding-top: 1rem;
|
774 |
+
}
|
775 |
+
|
776 |
+
/* أنماط إضافية للأيقونات والرموز */
|
777 |
+
.colored-icon {
|
778 |
+
background: var(--primary-gradient);
|
779 |
+
-webkit-background-clip: text;
|
780 |
+
background-clip: text;
|
781 |
+
color: transparent;
|
782 |
+
}
|
783 |
+
|
784 |
+
/* أنماط الشعار */
|
785 |
+
.logo {
|
786 |
+
display: flex;
|
787 |
+
align-items: center;
|
788 |
+
}
|
789 |
+
|
790 |
+
.logo-text {
|
791 |
+
font-weight: 800;
|
792 |
+
font-size: 1.25rem;
|
793 |
+
margin-right: 0.5rem;
|
794 |
+
background: var(--primary-gradient);
|
795 |
+
-webkit-background-clip: text;
|
796 |
+
background-clip: text;
|
797 |
+
color: transparent;
|
798 |
+
}
|
799 |
+
|
800 |
+
/* كلاس للخط العريض */
|
801 |
+
.bold {
|
802 |
+
font-weight: 700;
|
803 |
+
}
|
804 |
+
|
805 |
+
/* تحسين مظهر مخطط مرمايد للنظام */
|
806 |
+
.mermaid {
|
807 |
+
margin: 1.5rem 0;
|
808 |
+
}
|
809 |
+
|
810 |
+
/* تعديلات على شكل العنصر الجانبي */
|
811 |
+
.stSidebar {
|
812 |
+
background-color: white;
|
813 |
+
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.05);
|
814 |
+
}
|
815 |
+
|
816 |
+
[data-testid="stSidebarContent"] {
|
817 |
+
background: var(--sidebar-gradient);
|
818 |
+
}
|
819 |
+
|
820 |
+
/* تحسين شكل لوحة المعلومات */
|
821 |
+
.dashboard-card {
|
822 |
+
background: white;
|
823 |
+
border-radius: var(--border-radius);
|
824 |
+
padding: 1rem;
|
825 |
+
box-shadow: var(--card-shadow);
|
826 |
+
height: 100%;
|
827 |
+
position: relative;
|
828 |
+
overflow: hidden;
|
829 |
+
}
|
830 |
+
|
831 |
+
.dashboard-card::before {
|
832 |
+
content: "";
|
833 |
+
position: absolute;
|
834 |
+
top: 0;
|
835 |
+
left: 0;
|
836 |
+
right: 0;
|
837 |
+
height: 4px;
|
838 |
+
background: var(--primary-gradient);
|
839 |
+
}
|
840 |
+
|
841 |
+
.dashboard-value {
|
842 |
+
font-size: 2rem;
|
843 |
+
font-weight: 800;
|
844 |
+
color: var(--primary-color);
|
845 |
+
margin: 0.5rem 0;
|
846 |
+
}
|
847 |
+
|
848 |
+
.dashboard-title {
|
849 |
+
color: var(--text-medium);
|
850 |
+
font-size: 0.9rem;
|
851 |
+
margin-bottom: 0.5rem;
|
852 |
+
font-weight: 600;
|
853 |
+
}
|
854 |
+
|
855 |
+
.dashboard-change {
|
856 |
+
font-size: 0.8rem;
|
857 |
+
display: flex;
|
858 |
+
align-items: center;
|
859 |
+
}
|
860 |
+
|
861 |
+
.change-up {
|
862 |
+
color: var(--success-color);
|
863 |
+
}
|
864 |
+
|
865 |
+
.change-down {
|
866 |
+
color: var(--danger-color);
|
867 |
+
}
|
868 |
+
|
869 |
+
/* بالنسبة للشركة */
|
870 |
+
.company-info {
|
871 |
+
text-align: center;
|
872 |
+
background: white;
|
873 |
+
padding: 1rem;
|
874 |
+
border-radius: var(--border-radius);
|
875 |
+
margin: 1.5rem 0;
|
876 |
+
box-shadow: var(--card-shadow);
|
877 |
+
}
|
878 |
+
|
879 |
+
.company-logo {
|
880 |
+
max-width: 150px;
|
881 |
+
margin: 0 auto 1rem auto;
|
882 |
+
}
|
883 |
+
|
884 |
+
.company-name {
|
885 |
+
font-size: 1.2rem;
|
886 |
+
font-weight: 700;
|
887 |
+
color: var(--text-dark);
|
888 |
+
margin-bottom: 0.5rem;
|
889 |
+
}
|
890 |
+
|
891 |
+
.company-slogan {
|
892 |
+
font-size: 0.9rem;
|
893 |
+
color: var(--text-medium);
|
894 |
+
margin-bottom: 1rem;
|
895 |
+
}
|
896 |
+
|
897 |
+
.company-contact {
|
898 |
+
display: flex;
|
899 |
+
justify-content: center;
|
900 |
+
gap: 1rem;
|
901 |
+
margin-top: 1rem;
|
902 |
+
}
|
903 |
+
|
904 |
+
.contact-item {
|
905 |
+
display: flex;
|
906 |
+
align-items: center;
|
907 |
+
font-size: 0.85rem;
|
908 |
+
color: var(--text-medium);
|
909 |
+
}
|
910 |
+
|
911 |
+
.contact-icon {
|
912 |
+
margin-left: 0.3rem;
|
913 |
+
}
|
static/css/backup/rtl-fixes.css
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* تصحيحات RTL للتوافق مع streamlit */
|
2 |
+
|
3 |
+
/* تصحيحات عامة */
|
4 |
+
.stApp {
|
5 |
+
direction: rtl;
|
6 |
+
text-align: right;
|
7 |
+
}
|
8 |
+
|
9 |
+
/* تصحيحات القوائم */
|
10 |
+
.st-emotion-cache-r421ms.e1f1d6gn0 {
|
11 |
+
padding-right: 0;
|
12 |
+
padding-left: 1em;
|
13 |
+
}
|
14 |
+
|
15 |
+
/* تصحيحات العناوين */
|
16 |
+
h1, h2, h3, h4, h5, h6, .st-ae, .st-af, .st-ag, .st-ah, .st-ai, .st-aj {
|
17 |
+
text-align: right;
|
18 |
+
}
|
19 |
+
|
20 |
+
/* تصحيحات الجداول */
|
21 |
+
.stDataFrame {
|
22 |
+
direction: rtl;
|
23 |
+
}
|
24 |
+
|
25 |
+
.stDataFrame .dataframe {
|
26 |
+
text-align: right;
|
27 |
+
}
|
28 |
+
|
29 |
+
/* تصحيحات الأزرار */
|
30 |
+
.stButton button {
|
31 |
+
margin-right: 0;
|
32 |
+
margin-left: 0.5em;
|
33 |
+
}
|
34 |
+
|
35 |
+
/* تصحيحات شريط التمرير */
|
36 |
+
.stSlider div[data-baseweb="slider"] {
|
37 |
+
direction: ltr;
|
38 |
+
}
|
39 |
+
|
40 |
+
/* تصحيحات الاختيار المتعدد */
|
41 |
+
.stMultiSelect [data-baseweb="tag"] {
|
42 |
+
margin-right: 0;
|
43 |
+
margin-left: 0.5em;
|
44 |
+
}
|
45 |
+
|
46 |
+
/* تصحيحات الأعمدة */
|
47 |
+
.row-widget.stRadio > div {
|
48 |
+
flex-direction: row-reverse;
|
49 |
+
}
|
50 |
+
|
51 |
+
.row-widget.stCheckbox > div {
|
52 |
+
flex-direction: row-reverse;
|
53 |
+
}
|
54 |
+
|
55 |
+
/* تصحيحات مربع النص */
|
56 |
+
.stTextInput input {
|
57 |
+
text-
|
static/css/backup/styles.css
ADDED
@@ -0,0 +1,373 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* أنماط CSS للنظام */
|
2 |
+
|
3 |
+
/* تعيين اتجاه النص من اليمين إلى اليسار للغة العربية */
|
4 |
+
body {
|
5 |
+
direction: rtl;
|
6 |
+
text-align: right;
|
7 |
+
font-family: 'Tajawal', 'Cairo', sans-serif;
|
8 |
+
}
|
9 |
+
|
10 |
+
/* أنماط العناوين */
|
11 |
+
h1, h2, h3, h4, h5, h6 {
|
12 |
+
font-family: 'Tajawal', 'Cairo', sans-serif;
|
13 |
+
color: #333;
|
14 |
+
}
|
15 |
+
|
16 |
+
/* أنماط ترويسة الصفحة */
|
17 |
+
.header-container {
|
18 |
+
display: flex;
|
19 |
+
flex-direction: row;
|
20 |
+
justify-content: space-between;
|
21 |
+
align-items: center;
|
22 |
+
padding: 10px 0;
|
23 |
+
background-color: #f8f9fa;
|
24 |
+
border-radius: 10px;
|
25 |
+
margin-bottom: 20px;
|
26 |
+
}
|
27 |
+
|
28 |
+
.header-title {
|
29 |
+
margin-right: 20px;
|
30 |
+
}
|
31 |
+
|
32 |
+
.header-title h1 {
|
33 |
+
margin: 0;
|
34 |
+
font-size: 24px;
|
35 |
+
color: #2c3e50;
|
36 |
+
}
|
37 |
+
|
38 |
+
.header-title p {
|
39 |
+
margin: 0;
|
40 |
+
font-size: 14px;
|
41 |
+
color: #7f8c8d;
|
42 |
+
}
|
43 |
+
|
44 |
+
.header-info {
|
45 |
+
display: flex;
|
46 |
+
align-items: center;
|
47 |
+
}
|
48 |
+
|
49 |
+
.date-box {
|
50 |
+
display: flex;
|
51 |
+
background-color: #ff9a3c;
|
52 |
+
color: white;
|
53 |
+
border-radius: 8px;
|
54 |
+
padding: 5px 10px;
|
55 |
+
margin-left: 15px;
|
56 |
+
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
57 |
+
}
|
58 |
+
|
59 |
+
.date-day {
|
60 |
+
font-size: 24px;
|
61 |
+
font-weight: bold;
|
62 |
+
margin-left: 5px;
|
63 |
+
line-height: 1;
|
64 |
+
}
|
65 |
+
|
66 |
+
.date-info {
|
67 |
+
display: flex;
|
68 |
+
flex-direction: column;
|
69 |
+
font-size: 12px;
|
70 |
+
}
|
71 |
+
|
72 |
+
.date-month {
|
73 |
+
font-weight: bold;
|
74 |
+
line-height: 1.2;
|
75 |
+
}
|
76 |
+
|
77 |
+
.date-year {
|
78 |
+
line-height: 1;
|
79 |
+
}
|
80 |
+
|
81 |
+
/* أنماط قائمة التنقل */
|
82 |
+
.nav-menu {
|
83 |
+
margin: 10px 0;
|
84 |
+
}
|
85 |
+
|
86 |
+
.nav-menu ul {
|
87 |
+
display: flex;
|
88 |
+
list-style: none;
|
89 |
+
padding: 0;
|
90 |
+
margin: 0;
|
91 |
+
justify-content: flex-end;
|
92 |
+
}
|
93 |
+
|
94 |
+
.nav-menu li {
|
95 |
+
margin-left: 15px;
|
96 |
+
}
|
97 |
+
|
98 |
+
.nav-menu a {
|
99 |
+
display: flex;
|
100 |
+
align-items: center;
|
101 |
+
color: #2c3e50;
|
102 |
+
text-decoration: none;
|
103 |
+
padding: 5px 10px;
|
104 |
+
border-radius: 5px;
|
105 |
+
transition: background-color 0.3s;
|
106 |
+
}
|
107 |
+
|
108 |
+
.nav-menu a:hover {
|
109 |
+
background-color: #f0f0f0;
|
110 |
+
}
|
111 |
+
|
112 |
+
.nav-icon {
|
113 |
+
margin-left: 5px;
|
114 |
+
}
|
115 |
+
|
116 |
+
/* أنماط عنوان الوحدة */
|
117 |
+
.module-title {
|
118 |
+
color: #2c3e50;
|
119 |
+
font-size: 28px;
|
120 |
+
margin-bottom: 20px;
|
121 |
+
border-right: 5px solid #ff9a3c;
|
122 |
+
padding-right: 10px;
|
123 |
+
}
|
124 |
+
|
125 |
+
.main-title {
|
126 |
+
color: #2c3e50;
|
127 |
+
font-size: 32px;
|
128 |
+
text-align: center;
|
129 |
+
margin: 20px 0;
|
130 |
+
}
|
131 |
+
|
132 |
+
/* أنماط بطاقات المعلومات */
|
133 |
+
.info-card {
|
134 |
+
background-color: #f8f9fa;
|
135 |
+
border-radius: 10px;
|
136 |
+
padding: 20px;
|
137 |
+
margin-bottom: 20px;
|
138 |
+
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
139 |
+
}
|
140 |
+
|
141 |
+
.info-card h3 {
|
142 |
+
color: #333;
|
143 |
+
margin-top: 0;
|
144 |
+
margin-bottom: 10px;
|
145 |
+
}
|
146 |
+
|
147 |
+
.info-card p {
|
148 |
+
color: #666;
|
149 |
+
margin: 0;
|
150 |
+
}
|
151 |
+
|
152 |
+
/* أنماط الجداول */
|
153 |
+
.dataframe {
|
154 |
+
width: 100%;
|
155 |
+
border-collapse: collapse;
|
156 |
+
margin-bottom: 20px;
|
157 |
+
}
|
158 |
+
|
159 |
+
.dataframe th {
|
160 |
+
background-color: #f0f0f0;
|
161 |
+
color: #333;
|
162 |
+
text-align: right;
|
163 |
+
padding: 8px;
|
164 |
+
border: 1px solid #ddd;
|
165 |
+
}
|
166 |
+
|
167 |
+
.dataframe td {
|
168 |
+
padding: 8px;
|
169 |
+
border: 1px solid #ddd;
|
170 |
+
text-align: right;
|
171 |
+
}
|
172 |
+
|
173 |
+
.dataframe tr:nth-child(even) {
|
174 |
+
background-color: #f9f9f9;
|
175 |
+
}
|
176 |
+
|
177 |
+
.dataframe tr:hover {
|
178 |
+
background-color: #f0f0f0;
|
179 |
+
}
|
180 |
+
|
181 |
+
/* أنماط الأزرار */
|
182 |
+
button, .stButton>button {
|
183 |
+
background-color: #ff9a3c;
|
184 |
+
color: white;
|
185 |
+
border: none;
|
186 |
+
border-radius: 4px;
|
187 |
+
padding: 8px 16px;
|
188 |
+
cursor: pointer;
|
189 |
+
transition: background-color 0.3s;
|
190 |
+
}
|
191 |
+
|
192 |
+
button:hover, .stButton>button:hover {
|
193 |
+
background-color: #e67e22;
|
194 |
+
}
|
195 |
+
|
196 |
+
/* أنماط المخططات */
|
197 |
+
.plot-container {
|
198 |
+
margin: 20px 0;
|
199 |
+
}
|
200 |
+
|
201 |
+
/* أنماط بطاقات الابتكارات */
|
202 |
+
.innovation-card {
|
203 |
+
background-color: #f8f9fa;
|
204 |
+
border-radius: 10px;
|
205 |
+
padding: 15px;
|
206 |
+
margin-bottom: 20px;
|
207 |
+
border-right: 5px solid #ff9a3c;
|
208 |
+
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
209 |
+
}
|
210 |
+
|
211 |
+
.innovation-icon {
|
212 |
+
font-size: 24px;
|
213 |
+
margin-bottom: 10px;
|
214 |
+
}
|
215 |
+
|
216 |
+
.innovation-card h3 {
|
217 |
+
color: #333;
|
218 |
+
margin-bottom: 10px;
|
219 |
+
}
|
220 |
+
|
221 |
+
.innovation-card p {
|
222 |
+
color: #666;
|
223 |
+
font-size: 14px;
|
224 |
+
}
|
225 |
+
|
226 |
+
/* أنماط فريق التطوير */
|
227 |
+
.team-member {
|
228 |
+
text-align: center;
|
229 |
+
margin-bottom: 20px;
|
230 |
+
}
|
231 |
+
|
232 |
+
.team-member h3 {
|
233 |
+
color: #333;
|
234 |
+
margin-bottom: 5px;
|
235 |
+
font-size: 18px;
|
236 |
+
}
|
237 |
+
|
238 |
+
.team-member h4 {
|
239 |
+
color: #ff9a3c;
|
240 |
+
margin-top: 0;
|
241 |
+
margin-bottom: 10px;
|
242 |
+
font-size: 14px;
|
243 |
+
}
|
244 |
+
|
245 |
+
.team-member p {
|
246 |
+
color: #666;
|
247 |
+
font-size: 12px;
|
248 |
+
}
|
249 |
+
|
250 |
+
.avatar {
|
251 |
+
background-color: #ff9a3c;
|
252 |
+
color: white;
|
253 |
+
width: 100px;
|
254 |
+
height: 100px;
|
255 |
+
border-radius: 50%;
|
256 |
+
display: flex;
|
257 |
+
justify-content: center;
|
258 |
+
align-items: center;
|
259 |
+
margin: 0 auto 15px auto;
|
260 |
+
font-size: 36px;
|
261 |
+
}
|
262 |
+
|
263 |
+
/* أنماط تذييل الصفحة */
|
264 |
+
.footer {
|
265 |
+
text-align: center;
|
266 |
+
color: #7f8c8d;
|
267 |
+
font-size: 12px;
|
268 |
+
margin-top: 30px;
|
269 |
+
margin-bottom: 10px;
|
270 |
+
}
|
271 |
+
|
272 |
+
/* أنماط رسائل التنبيه */
|
273 |
+
.alert {
|
274 |
+
padding: 10px 15px;
|
275 |
+
border-radius: 4px;
|
276 |
+
margin-bottom: 15px;
|
277 |
+
}
|
278 |
+
|
279 |
+
.alert-info {
|
280 |
+
background-color: #d1ecf1;
|
281 |
+
color: #0c5460;
|
282 |
+
border: 1px solid #bee5eb;
|
283 |
+
}
|
284 |
+
|
285 |
+
.alert-success {
|
286 |
+
background-color: #d4edda;
|
287 |
+
color: #155724;
|
288 |
+
border: 1px solid #c3e6cb;
|
289 |
+
}
|
290 |
+
|
291 |
+
.alert-warning {
|
292 |
+
background-color: #fff3cd;
|
293 |
+
color: #856404;
|
294 |
+
border: 1px solid #ffeeba;
|
295 |
+
}
|
296 |
+
|
297 |
+
.alert-danger {
|
298 |
+
background-color: #f8d7da;
|
299 |
+
color: #721c24;
|
300 |
+
border: 1px solid #f5c6cb;
|
301 |
+
}
|
302 |
+
|
303 |
+
/* أنماط الأيقونات */
|
304 |
+
.icon {
|
305 |
+
font-size: 18px;
|
306 |
+
margin-left: 5px;
|
307 |
+
}
|
308 |
+
|
309 |
+
/* أنماط التبويبات */
|
310 |
+
.stTabs [data-baseweb="tab-list"] {
|
311 |
+
gap: 1px;
|
312 |
+
}
|
313 |
+
|
314 |
+
.stTabs [data-baseweb="tab"] {
|
315 |
+
height: 50px;
|
316 |
+
white-space: pre-wrap;
|
317 |
+
background-color: white;
|
318 |
+
border-radius: 4px 4px 0 0;
|
319 |
+
gap: 1px;
|
320 |
+
padding-top: 10px;
|
321 |
+
padding-bottom: 10px;
|
322 |
+
}
|
323 |
+
|
324 |
+
.stTabs [aria-selected="true"] {
|
325 |
+
background-color: #ff9a3c !important;
|
326 |
+
color: white !important;
|
327 |
+
}
|
328 |
+
|
329 |
+
/* أنماط مخصصة لدعم اللغة العربية في إدخالات النصوص والأرقام */
|
330 |
+
input, textarea, .stTextInput>div>div>input, .stNumberInput>div>div>input {
|
331 |
+
direction: rtl;
|
332 |
+
text-align: right;
|
333 |
+
}
|
334 |
+
|
335 |
+
/* أنماط قائمة الخيارات */
|
336 |
+
.stSelectbox [data-baseweb="select"] {
|
337 |
+
direction: rtl;
|
338 |
+
text-align: right;
|
339 |
+
}
|
340 |
+
|
341 |
+
/* أنماط تحرير البيانات */
|
342 |
+
.stDataEditor {
|
343 |
+
direction: rtl;
|
344 |
+
}
|
345 |
+
|
346 |
+
/* أنماط للنسخة المحمولة */
|
347 |
+
@media (max-width: 768px) {
|
348 |
+
.header-container {
|
349 |
+
flex-direction: column;
|
350 |
+
align-items: flex-start;
|
351 |
+
}
|
352 |
+
|
353 |
+
.header-info {
|
354 |
+
margin-top: 10px;
|
355 |
+
}
|
356 |
+
|
357 |
+
.nav-menu ul {
|
358 |
+
flex-wrap: wrap;
|
359 |
+
}
|
360 |
+
|
361 |
+
.nav-menu li {
|
362 |
+
margin-bottom: 5px;
|
363 |
+
margin-left: 10px;
|
364 |
+
}
|
365 |
+
|
366 |
+
.module-title {
|
367 |
+
font-size: 24px;
|
368 |
+
}
|
369 |
+
|
370 |
+
.main-title {
|
371 |
+
font-size: 24px;
|
372 |
+
}
|
373 |
+
}
|
static/css/rtl-fixes.css
CHANGED
@@ -1,66 +1,57 @@
|
|
1 |
-
/*
|
2 |
-
|
3 |
-
/*
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
}
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
text-align: right;
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
.
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
}
|
39 |
-
|
40 |
-
/*
|
41 |
-
.
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
.
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
direction: rtl;
|
59 |
-
text-align: right;
|
60 |
-
}
|
61 |
-
|
62 |
-
/* تحسين ظهور قوائم الاختيار */
|
63 |
-
.stMultiSelect div:first-child,
|
64 |
-
.stSelectbox div:first-child {
|
65 |
-
text-align: right;
|
66 |
-
}
|
|
|
1 |
+
/* تصحيحات RTL للتوافق مع streamlit */
|
2 |
+
|
3 |
+
/* تصحيحات عامة */
|
4 |
+
.stApp {
|
5 |
+
direction: rtl;
|
6 |
+
text-align: right;
|
7 |
+
}
|
8 |
+
|
9 |
+
/* تصحيحات القوائم */
|
10 |
+
.st-emotion-cache-r421ms.e1f1d6gn0 {
|
11 |
+
padding-right: 0;
|
12 |
+
padding-left: 1em;
|
13 |
+
}
|
14 |
+
|
15 |
+
/* تصحيحات العناوين */
|
16 |
+
h1, h2, h3, h4, h5, h6, .st-ae, .st-af, .st-ag, .st-ah, .st-ai, .st-aj {
|
17 |
+
text-align: right;
|
18 |
+
}
|
19 |
+
|
20 |
+
/* تصحيحات الجداول */
|
21 |
+
.stDataFrame {
|
22 |
+
direction: rtl;
|
23 |
+
}
|
24 |
+
|
25 |
+
.stDataFrame .dataframe {
|
26 |
+
text-align: right;
|
27 |
+
}
|
28 |
+
|
29 |
+
/* تصحيحات الأزرار */
|
30 |
+
.stButton button {
|
31 |
+
margin-right: 0;
|
32 |
+
margin-left: 0.5em;
|
33 |
+
}
|
34 |
+
|
35 |
+
/* تصحيحات شريط التمرير */
|
36 |
+
.stSlider div[data-baseweb="slider"] {
|
37 |
+
direction: ltr;
|
38 |
+
}
|
39 |
+
|
40 |
+
/* تصحيحات الاختيار المتعدد */
|
41 |
+
.stMultiSelect [data-baseweb="tag"] {
|
42 |
+
margin-right: 0;
|
43 |
+
margin-left: 0.5em;
|
44 |
+
}
|
45 |
+
|
46 |
+
/* تصحيحات الأعمدة */
|
47 |
+
.row-widget.stRadio > div {
|
48 |
+
flex-direction: row-reverse;
|
49 |
+
}
|
50 |
+
|
51 |
+
.row-widget.stCheckbox > div {
|
52 |
+
flex-direction: row-reverse;
|
53 |
+
}
|
54 |
+
|
55 |
+
/* تصحيحات مربع النص */
|
56 |
+
.stTextInput input {
|
57 |
+
text-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/css/unified_design_system.css
ADDED
@@ -0,0 +1,488 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* نظام تصميم موحد لنظام تحليل المناقصات */
|
2 |
+
@import url('https://fonts.googleapis.com/css2?family=Almarai:wght@300;400;700;800&display=swap');
|
3 |
+
|
4 |
+
:root {
|
5 |
+
/* الألوان الأساسية */
|
6 |
+
--primary-color: #2563eb;
|
7 |
+
--primary-light: #dbeafe;
|
8 |
+
--primary-dark: #1e40af;
|
9 |
+
--secondary-color: #10b981;
|
10 |
+
--secondary-light: #d1fae5;
|
11 |
+
--secondary-dark: #047857;
|
12 |
+
|
13 |
+
/* ألوان النص */
|
14 |
+
--text-dark: #1e293b;
|
15 |
+
--text-medium: #475569;
|
16 |
+
--text-light: #94a3b8;
|
17 |
+
--text-white: #ffffff;
|
18 |
+
|
19 |
+
/* ألوان الخلفية */
|
20 |
+
--background-light: #f8fafc;
|
21 |
+
--background-white: #ffffff;
|
22 |
+
--background-dark: #0f172a;
|
23 |
+
|
24 |
+
/* ألوان الحدود */
|
25 |
+
--border-color: #e2e8f0;
|
26 |
+
--border-color-dark: #cbd5e1;
|
27 |
+
|
28 |
+
/* ألوان الحالة */
|
29 |
+
--success-color: #22c55e;
|
30 |
+
--warning-color: #f59e0b;
|
31 |
+
--danger-color: #ef4444;
|
32 |
+
--info-color: #3b82f6;
|
33 |
+
|
34 |
+
/* الظلال */
|
35 |
+
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
36 |
+
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
37 |
+
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
38 |
+
|
39 |
+
/* نصف قطر الحدود */
|
40 |
+
--border-radius-sm: 0.25rem;
|
41 |
+
--border-radius-md: 0.5rem;
|
42 |
+
--border-radius-lg: 0.75rem;
|
43 |
+
--border-radius-xl: 1rem;
|
44 |
+
--border-radius-full: 9999px;
|
45 |
+
|
46 |
+
/* المسافات */
|
47 |
+
--spacing-xs: 0.25rem;
|
48 |
+
--spacing-sm: 0.5rem;
|
49 |
+
--spacing-md: 1rem;
|
50 |
+
--spacing-lg: 1.5rem;
|
51 |
+
--spacing-xl: 2rem;
|
52 |
+
--spacing-2xl: 3rem;
|
53 |
+
|
54 |
+
/* الخط */
|
55 |
+
--font-family: 'Almarai', sans-serif;
|
56 |
+
--font-size-xs: 0.75rem;
|
57 |
+
--font-size-sm: 0.875rem;
|
58 |
+
--font-size-md: 1rem;
|
59 |
+
--font-size-lg: 1.125rem;
|
60 |
+
--font-size-xl: 1.25rem;
|
61 |
+
--font-size-2xl: 1.5rem;
|
62 |
+
--font-size-3xl: 1.875rem;
|
63 |
+
--font-size-4xl: 2.25rem;
|
64 |
+
|
65 |
+
/* الانتقالات */
|
66 |
+
--transition-fast: 150ms;
|
67 |
+
--transition-normal: 300ms;
|
68 |
+
--transition-slow: 500ms;
|
69 |
+
}
|
70 |
+
|
71 |
+
/* إعدادات عامة */
|
72 |
+
body {
|
73 |
+
font-family: var(--font-family);
|
74 |
+
color: var(--text-dark);
|
75 |
+
background-color: var(--background-light);
|
76 |
+
direction: rtl;
|
77 |
+
text-align: right;
|
78 |
+
line-height: 1.5;
|
79 |
+
}
|
80 |
+
|
81 |
+
.stApp {
|
82 |
+
direction: rtl;
|
83 |
+
}
|
84 |
+
|
85 |
+
/* تصحيحات RTL لـ Streamlit */
|
86 |
+
.css-1544g2n {
|
87 |
+
padding-left: 1rem !important;
|
88 |
+
padding-right: 1rem !important;
|
89 |
+
}
|
90 |
+
|
91 |
+
.css-1kyxreq {
|
92 |
+
justify-content: flex-start !important;
|
93 |
+
}
|
94 |
+
|
95 |
+
/* العناوين */
|
96 |
+
h1, h2, h3, h4, h5, h6 {
|
97 |
+
font-family: var(--font-family);
|
98 |
+
font-weight: 700;
|
99 |
+
margin-bottom: var(--spacing-md);
|
100 |
+
color: var(--text-dark);
|
101 |
+
}
|
102 |
+
|
103 |
+
.main-title {
|
104 |
+
font-size: var(--font-size-3xl);
|
105 |
+
font-weight: 800;
|
106 |
+
color: var(--primary-dark);
|
107 |
+
margin-bottom: var(--spacing-lg);
|
108 |
+
padding-bottom: var(--spacing-sm);
|
109 |
+
border-bottom: 2px solid var(--primary-light);
|
110 |
+
}
|
111 |
+
|
112 |
+
.module-title {
|
113 |
+
font-size: var(--font-size-xl);
|
114 |
+
font-weight: 700;
|
115 |
+
color: var(--text-dark);
|
116 |
+
margin-top: var(--spacing-xl);
|
117 |
+
margin-bottom: var(--spacing-md);
|
118 |
+
}
|
119 |
+
|
120 |
+
/* الشريط الجانبي */
|
121 |
+
.sidebar-title {
|
122 |
+
font-size: var(--font-size-xl);
|
123 |
+
font-weight: 700;
|
124 |
+
color: var(--primary-color);
|
125 |
+
margin-bottom: var(--spacing-md);
|
126 |
+
text-align: center;
|
127 |
+
}
|
128 |
+
|
129 |
+
.sidebar-divider {
|
130 |
+
height: 1px;
|
131 |
+
background-color: var(--border-color);
|
132 |
+
margin: var(--spacing-md) 0;
|
133 |
+
}
|
134 |
+
|
135 |
+
.sidebar-footer {
|
136 |
+
position: absolute;
|
137 |
+
bottom: 0;
|
138 |
+
left: 0;
|
139 |
+
right: 0;
|
140 |
+
padding: var(--spacing-md);
|
141 |
+
text-align: center;
|
142 |
+
font-size: var(--font-size-xs);
|
143 |
+
color: var(--text-light);
|
144 |
+
border-top: 1px solid var(--border-color);
|
145 |
+
}
|
146 |
+
|
147 |
+
/* صندوق التاريخ */
|
148 |
+
.date-box {
|
149 |
+
display: flex;
|
150 |
+
align-items: center;
|
151 |
+
background-color: var(--primary-light);
|
152 |
+
border-radius: var(--border-radius-md);
|
153 |
+
padding: var(--spacing-sm);
|
154 |
+
margin-bottom: var(--spacing-md);
|
155 |
+
}
|
156 |
+
|
157 |
+
.date-day {
|
158 |
+
font-size: var(--font-size-3xl);
|
159 |
+
font-weight: 800;
|
160 |
+
color: var(--primary-color);
|
161 |
+
margin-left: var(--spacing-md);
|
162 |
+
line-height: 1;
|
163 |
+
}
|
164 |
+
|
165 |
+
.date-info {
|
166 |
+
display: flex;
|
167 |
+
flex-direction: column;
|
168 |
+
}
|
169 |
+
|
170 |
+
.date-month {
|
171 |
+
font-size: var(--font-size-md);
|
172 |
+
font-weight: 700;
|
173 |
+
color: var(--primary-dark);
|
174 |
+
}
|
175 |
+
|
176 |
+
.date-year {
|
177 |
+
font-size: var(--font-size-sm);
|
178 |
+
color: var(--text-medium);
|
179 |
+
}
|
180 |
+
|
181 |
+
/* البطاقات */
|
182 |
+
.card {
|
183 |
+
background-color: var(--background-white);
|
184 |
+
border-radius: var(--border-radius-lg);
|
185 |
+
box-shadow: var(--shadow-md);
|
186 |
+
margin-bottom: var(--spacing-lg);
|
187 |
+
overflow: hidden;
|
188 |
+
}
|
189 |
+
|
190 |
+
.card-header {
|
191 |
+
padding: var(--spacing-md);
|
192 |
+
border-bottom: 1px solid var(--border-color);
|
193 |
+
background-color: var(--background-light);
|
194 |
+
}
|
195 |
+
|
196 |
+
.card-title {
|
197 |
+
font-size: var(--font-size-lg);
|
198 |
+
font-weight: 700;
|
199 |
+
color: var(--text-dark);
|
200 |
+
margin: 0;
|
201 |
+
}
|
202 |
+
|
203 |
+
.card-body {
|
204 |
+
padding: var(--spacing-lg);
|
205 |
+
}
|
206 |
+
|
207 |
+
/* بطاقات الإحصائيات */
|
208 |
+
.stat-card {
|
209 |
+
background-color: var(--background-white);
|
210 |
+
border-radius: var(--border-radius-lg);
|
211 |
+
box-shadow: var(--shadow-md);
|
212 |
+
padding: var(--spacing-lg);
|
213 |
+
margin-bottom: var(--spacing-lg);
|
214 |
+
text-align: center;
|
215 |
+
transition: transform var(--transition-normal), box-shadow var(--transition-normal);
|
216 |
+
}
|
217 |
+
|
218 |
+
.stat-card:hover {
|
219 |
+
transform: translateY(-5px);
|
220 |
+
box-shadow: var(--shadow-lg);
|
221 |
+
}
|
222 |
+
|
223 |
+
.stat-icon {
|
224 |
+
font-size: var(--font-size-3xl);
|
225 |
+
color: var(--primary-color);
|
226 |
+
margin-bottom: var(--spacing-md);
|
227 |
+
}
|
228 |
+
|
229 |
+
.stat-value {
|
230 |
+
font-size: var(--font-size-3xl);
|
231 |
+
font-weight: 800;
|
232 |
+
color: var(--text-dark);
|
233 |
+
margin-bottom: var(--spacing-xs);
|
234 |
+
}
|
235 |
+
|
236 |
+
.stat-label {
|
237 |
+
font-size: var(--font-size-md);
|
238 |
+
color: var(--text-medium);
|
239 |
+
margin-bottom: var(--spacing-sm);
|
240 |
+
}
|
241 |
+
|
242 |
+
.stat-change {
|
243 |
+
font-size: var(--font-size-sm);
|
244 |
+
font-weight: 600;
|
245 |
+
padding: var(--spacing-xs) var(--spacing-sm);
|
246 |
+
border-radius: var(--border-radius-full);
|
247 |
+
display: inline-block;
|
248 |
+
}
|
249 |
+
|
250 |
+
.stat-change.positive {
|
251 |
+
color: var(--success-color);
|
252 |
+
background-color: rgba(34, 197, 94, 0.1);
|
253 |
+
}
|
254 |
+
|
255 |
+
.stat-change.negative {
|
256 |
+
color: var(--danger-color);
|
257 |
+
background-color: rgba(239, 68, 68, 0.1);
|
258 |
+
}
|
259 |
+
|
260 |
+
/* بطاقات الابتكارات */
|
261 |
+
.innovation-card {
|
262 |
+
background-color: var(--background-white);
|
263 |
+
border-radius: var(--border-radius-lg);
|
264 |
+
box-shadow: var(--shadow-md);
|
265 |
+
padding: var(--spacing-lg);
|
266 |
+
margin-bottom: var(--spacing-lg);
|
267 |
+
text-align: center;
|
268 |
+
transition: transform var(--transition-normal), box-shadow var(--transition-normal);
|
269 |
+
height: 100%;
|
270 |
+
}
|
271 |
+
|
272 |
+
.innovation-card:hover {
|
273 |
+
transform: translateY(-5px);
|
274 |
+
box-shadow: var(--shadow-lg);
|
275 |
+
}
|
276 |
+
|
277 |
+
.innovation-icon {
|
278 |
+
font-size: var(--font-size-3xl);
|
279 |
+
color: var(--primary-color);
|
280 |
+
margin-bottom: var(--spacing-md);
|
281 |
+
background-color: var(--primary-light);
|
282 |
+
width: 80px;
|
283 |
+
height: 80px;
|
284 |
+
border-radius: var(--border-radius-full);
|
285 |
+
display: flex;
|
286 |
+
align-items: center;
|
287 |
+
justify-content: center;
|
288 |
+
margin-left: auto;
|
289 |
+
margin-right: auto;
|
290 |
+
}
|
291 |
+
|
292 |
+
.innovation-card h3 {
|
293 |
+
font-size: var(--font-size-xl);
|
294 |
+
font-weight: 700;
|
295 |
+
color: var(--text-dark);
|
296 |
+
margin-bottom: var(--spacing-sm);
|
297 |
+
}
|
298 |
+
|
299 |
+
.innovation-card p {
|
300 |
+
font-size: var(--font-size-md);
|
301 |
+
color: var(--text-medium);
|
302 |
+
}
|
303 |
+
|
304 |
+
/* الأزرار */
|
305 |
+
.stButton > button {
|
306 |
+
font-family: var(--font-family) !important;
|
307 |
+
font-weight: 600 !important;
|
308 |
+
border-radius: var(--border-radius-md) !important;
|
309 |
+
transition: all var(--transition-fast) !important;
|
310 |
+
}
|
311 |
+
|
312 |
+
.stButton > button:hover {
|
313 |
+
transform: translateY(-2px);
|
314 |
+
}
|
315 |
+
|
316 |
+
.stButton > button[data-baseweb="button"] {
|
317 |
+
background-color: var(--primary-color) !important;
|
318 |
+
}
|
319 |
+
|
320 |
+
/* حقول الإدخال */
|
321 |
+
.stTextInput > div > div > input {
|
322 |
+
font-family: var(--font-family) !important;
|
323 |
+
border-radius: var(--border-radius-md) !important;
|
324 |
+
}
|
325 |
+
|
326 |
+
.stTextArea > div > div > textarea {
|
327 |
+
font-family: var(--font-family) !important;
|
328 |
+
border-radius: var(--border-radius-md) !important;
|
329 |
+
}
|
330 |
+
|
331 |
+
/* القوائم المنسدلة */
|
332 |
+
.stSelectbox > div > div > div {
|
333 |
+
font-family: var(--font-family) !important;
|
334 |
+
border-radius: var(--border-radius-md) !important;
|
335 |
+
}
|
336 |
+
|
337 |
+
/* تبويبات */
|
338 |
+
.stTabs {
|
339 |
+
background-color: var(--background-white);
|
340 |
+
border-radius: var(--border-radius-lg);
|
341 |
+
box-shadow: var(--shadow-md);
|
342 |
+
padding: var(--spacing-md);
|
343 |
+
margin-bottom: var(--spacing-lg);
|
344 |
+
}
|
345 |
+
|
346 |
+
/* تذييل الصفحة */
|
347 |
+
.footer {
|
348 |
+
margin-top: var(--spacing-2xl);
|
349 |
+
padding-top: var(--spacing-lg);
|
350 |
+
border-top: 1px solid var(--border-color);
|
351 |
+
text-align: center;
|
352 |
+
color: var(--text-medium);
|
353 |
+
font-size: var(--font-size-sm);
|
354 |
+
}
|
355 |
+
|
356 |
+
/* تنبيهات */
|
357 |
+
.stAlert {
|
358 |
+
border-radius: var(--border-radius-md) !important;
|
359 |
+
}
|
360 |
+
|
361 |
+
/* تحسينات للجداول */
|
362 |
+
.dataframe {
|
363 |
+
width: 100%;
|
364 |
+
border-collapse: separate;
|
365 |
+
border-spacing: 0;
|
366 |
+
margin-bottom: var(--spacing-lg);
|
367 |
+
border-radius: var(--border-radius-lg);
|
368 |
+
overflow: hidden;
|
369 |
+
box-shadow: var(--shadow-md);
|
370 |
+
}
|
371 |
+
|
372 |
+
.dataframe th {
|
373 |
+
background-color: var(--primary-color);
|
374 |
+
color: var(--text-white);
|
375 |
+
text-align: right;
|
376 |
+
padding: var(--spacing-md);
|
377 |
+
font-weight: 600;
|
378 |
+
border: none;
|
379 |
+
}
|
380 |
+
|
381 |
+
.dataframe td {
|
382 |
+
padding: var(--spacing-md);
|
383 |
+
border-bottom: 1px solid var(--border-color);
|
384 |
+
text-align: right;
|
385 |
+
background-color: var(--background-white);
|
386 |
+
}
|
387 |
+
|
388 |
+
.dataframe tr:nth-child(even) td {
|
389 |
+
background-color: rgba(248, 249, 250, 0.7);
|
390 |
+
}
|
391 |
+
|
392 |
+
.dataframe tr:hover td {
|
393 |
+
background-color: var(--primary-light);
|
394 |
+
}
|
395 |
+
|
396 |
+
/* تحسينات للرسوم البيانية */
|
397 |
+
.stPlotlyChart {
|
398 |
+
background-color: var(--background-white);
|
399 |
+
border-radius: var(--border-radius-lg);
|
400 |
+
box-shadow: var(--shadow-md);
|
401 |
+
padding: var(--spacing-md);
|
402 |
+
margin-bottom: var(--spacing-lg);
|
403 |
+
}
|
404 |
+
|
405 |
+
/* تحسينات للتنقل */
|
406 |
+
.stRadio > div {
|
407 |
+
display: flex;
|
408 |
+
flex-direction: column;
|
409 |
+
}
|
410 |
+
|
411 |
+
.stRadio > div > label {
|
412 |
+
font-family: var(--font-family) !important;
|
413 |
+
margin-bottom: var(--spacing-sm);
|
414 |
+
}
|
415 |
+
|
416 |
+
/* تحسينات للشاشات الصغيرة */
|
417 |
+
@media (max-width: 768px) {
|
418 |
+
.main-title {
|
419 |
+
font-size: var(--font-size-2xl);
|
420 |
+
}
|
421 |
+
|
422 |
+
.module-title {
|
423 |
+
font-size: var(--font-size-lg);
|
424 |
+
}
|
425 |
+
|
426 |
+
.stat-card {
|
427 |
+
padding: var(--spacing-md);
|
428 |
+
}
|
429 |
+
|
430 |
+
.stat-value {
|
431 |
+
font-size: var(--font-size-2xl);
|
432 |
+
}
|
433 |
+
|
434 |
+
.innovation-card {
|
435 |
+
padding: var(--spacing-md);
|
436 |
+
}
|
437 |
+
|
438 |
+
.innovation-icon {
|
439 |
+
width: 60px;
|
440 |
+
height: 60px;
|
441 |
+
font-size: var(--font-size-2xl);
|
442 |
+
}
|
443 |
+
|
444 |
+
.card-body {
|
445 |
+
padding: var(--spacing-md);
|
446 |
+
}
|
447 |
+
}
|
448 |
+
|
449 |
+
/* تحسينات للشاشات الكبيرة */
|
450 |
+
@media (min-width: 1200px) {
|
451 |
+
.main-title {
|
452 |
+
font-size: var(--font-size-4xl);
|
453 |
+
}
|
454 |
+
|
455 |
+
.module-title {
|
456 |
+
font-size: var(--font-size-2xl);
|
457 |
+
}
|
458 |
+
|
459 |
+
.stat-value {
|
460 |
+
font-size: var(--font-size-4xl);
|
461 |
+
}
|
462 |
+
|
463 |
+
.innovation-icon {
|
464 |
+
width: 100px;
|
465 |
+
height: 100px;
|
466 |
+
font-size: var(--font-size-4xl);
|
467 |
+
}
|
468 |
+
}
|
469 |
+
|
470 |
+
/* تحسينات للطباعة */
|
471 |
+
@media print {
|
472 |
+
.stSidebar {
|
473 |
+
display: none !important;
|
474 |
+
}
|
475 |
+
|
476 |
+
.main-title {
|
477 |
+
font-size: var(--font-size-2xl);
|
478 |
+
}
|
479 |
+
|
480 |
+
.card, .stat-card, .innovation-card {
|
481 |
+
box-shadow: none;
|
482 |
+
border: 1px solid var(--border-color);
|
483 |
+
}
|
484 |
+
|
485 |
+
.footer {
|
486 |
+
margin-top: var(--spacing-lg);
|
487 |
+
}
|
488 |
+
}
|
static/js/enhanced_main.js
ADDED
@@ -0,0 +1,310 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// ملف JavaScript المحسن لنظام تحليل المناقصات
|
2 |
+
document.addEventListener('DOMContentLoaded', function() {
|
3 |
+
// تهيئة المكونات التفاعلية
|
4 |
+
initializeComponents();
|
5 |
+
|
6 |
+
// إضافة استجابة للأحداث
|
7 |
+
setupEventListeners();
|
8 |
+
|
9 |
+
// تحسين تجربة المستخدم
|
10 |
+
enhanceUserExperience();
|
11 |
+
});
|
12 |
+
|
13 |
+
// تهيئة المكونات التفاعلية
|
14 |
+
function initializeComponents() {
|
15 |
+
// تهيئة القوائم المنسدلة
|
16 |
+
const dropdowns = document.querySelectorAll('.dropdown');
|
17 |
+
if (dropdowns) {
|
18 |
+
dropdowns.forEach(dropdown => {
|
19 |
+
dropdown.addEventListener('click', function() {
|
20 |
+
this.classList.toggle('active');
|
21 |
+
});
|
22 |
+
});
|
23 |
+
}
|
24 |
+
|
25 |
+
// تهيئة التبويبات
|
26 |
+
const tabButtons = document.querySelectorAll('.tab-button');
|
27 |
+
if (tabButtons) {
|
28 |
+
tabButtons.forEach(button => {
|
29 |
+
button.addEventListener('click', function() {
|
30 |
+
const tabId = this.getAttribute('data-tab');
|
31 |
+
|
32 |
+
// إزالة الفئة النشطة من جميع الأزرار
|
33 |
+
document.querySelectorAll('.tab-button').forEach(btn => {
|
34 |
+
btn.classList.remove('active');
|
35 |
+
});
|
36 |
+
|
37 |
+
// إزالة الفئة النشطة من جميع المحتويات
|
38 |
+
document.querySelectorAll('.tab-content').forEach(content => {
|
39 |
+
content.classList.remove('active');
|
40 |
+
});
|
41 |
+
|
42 |
+
// إضافة الفئة النشطة للزر والمحتوى المحدد
|
43 |
+
this.classList.add('active');
|
44 |
+
document.getElementById(tabId).classList.add('active');
|
45 |
+
});
|
46 |
+
});
|
47 |
+
}
|
48 |
+
|
49 |
+
// تهيئة الرسائل المنبثقة
|
50 |
+
const closeButtons = document.querySelectorAll('.alert .close');
|
51 |
+
if (closeButtons) {
|
52 |
+
closeButtons.forEach(button => {
|
53 |
+
button.addEventListener('click', function() {
|
54 |
+
this.parentElement.style.display = 'none';
|
55 |
+
});
|
56 |
+
});
|
57 |
+
}
|
58 |
+
}
|
59 |
+
|
60 |
+
// إعداد مستمعي الأحداث
|
61 |
+
function setupEventListeners() {
|
62 |
+
// مستمع لتغيير السمة
|
63 |
+
const themeToggle = document.getElementById('theme-toggle');
|
64 |
+
if (themeToggle) {
|
65 |
+
themeToggle.addEventListener('change', function() {
|
66 |
+
if (this.checked) {
|
67 |
+
document.body.classList.add('dark-theme');
|
68 |
+
localStorage.setItem('theme', 'dark');
|
69 |
+
} else {
|
70 |
+
document.body.classList.remove('dark-theme');
|
71 |
+
localStorage.setItem('theme', 'light');
|
72 |
+
}
|
73 |
+
});
|
74 |
+
}
|
75 |
+
|
76 |
+
// مستمع لتغيير حجم الخط
|
77 |
+
const fontSizeControls = document.querySelectorAll('.font-size-control');
|
78 |
+
if (fontSizeControls) {
|
79 |
+
fontSizeControls.forEach(control => {
|
80 |
+
control.addEventListener('click', function() {
|
81 |
+
const size = this.getAttribute('data-size');
|
82 |
+
document.body.classList.remove('font-small', 'font-medium', 'font-large', 'font-xlarge');
|
83 |
+
document.body.classList.add(`font-${size}`);
|
84 |
+
localStorage.setItem('fontSize', size);
|
85 |
+
});
|
86 |
+
});
|
87 |
+
}
|
88 |
+
|
89 |
+
// مستمع لأحداث التمرير
|
90 |
+
window.addEventListener('scroll', function() {
|
91 |
+
const header = document.querySelector('.main-header');
|
92 |
+
if (header) {
|
93 |
+
if (window.scrollY > 50) {
|
94 |
+
header.classList.add('scrolled');
|
95 |
+
} else {
|
96 |
+
header.classList.remove('scrolled');
|
97 |
+
}
|
98 |
+
}
|
99 |
+
});
|
100 |
+
}
|
101 |
+
|
102 |
+
// تحسين تجربة المستخدم
|
103 |
+
function enhanceUserExperience() {
|
104 |
+
// استعادة إعدادات المستخدم من التخزين المحلي
|
105 |
+
restoreUserSettings();
|
106 |
+
|
107 |
+
// تحسين أوقات التحميل
|
108 |
+
lazyLoadImages();
|
109 |
+
|
110 |
+
// تحسين التنقل
|
111 |
+
enhanceNavigation();
|
112 |
+
}
|
113 |
+
|
114 |
+
// استعادة إعدادات المستخدم
|
115 |
+
function restoreUserSettings() {
|
116 |
+
// استعادة السمة
|
117 |
+
const savedTheme = localStorage.getItem('theme');
|
118 |
+
if (savedTheme === 'dark') {
|
119 |
+
document.body.classList.add('dark-theme');
|
120 |
+
const themeToggle = document.getElementById('theme-toggle');
|
121 |
+
if (themeToggle) {
|
122 |
+
themeToggle.checked = true;
|
123 |
+
}
|
124 |
+
}
|
125 |
+
|
126 |
+
// استعادة حجم الخط
|
127 |
+
const savedFontSize = localStorage.getItem('fontSize');
|
128 |
+
if (savedFontSize) {
|
129 |
+
document.body.classList.add(`font-${savedFontSize}`);
|
130 |
+
}
|
131 |
+
}
|
132 |
+
|
133 |
+
// تحميل الصور بكسل
|
134 |
+
function lazyLoadImages() {
|
135 |
+
const lazyImages = document.querySelectorAll('img.lazy');
|
136 |
+
if (lazyImages) {
|
137 |
+
if ('IntersectionObserver' in window) {
|
138 |
+
const imageObserver = new IntersectionObserver(function(entries, observer) {
|
139 |
+
entries.forEach(function(entry) {
|
140 |
+
if (entry.isIntersecting) {
|
141 |
+
const image = entry.target;
|
142 |
+
image.src = image.dataset.src;
|
143 |
+
image.classList.remove('lazy');
|
144 |
+
imageObserver.unobserve(image);
|
145 |
+
}
|
146 |
+
});
|
147 |
+
});
|
148 |
+
|
149 |
+
lazyImages.forEach(function(image) {
|
150 |
+
imageObserver.observe(image);
|
151 |
+
});
|
152 |
+
} else {
|
153 |
+
// Fallback for browsers that don't support IntersectionObserver
|
154 |
+
lazyImages.forEach(function(image) {
|
155 |
+
image.src = image.dataset.src;
|
156 |
+
image.classList.remove('lazy');
|
157 |
+
});
|
158 |
+
}
|
159 |
+
}
|
160 |
+
}
|
161 |
+
|
162 |
+
// تحسين التنقل
|
163 |
+
function enhanceNavigation() {
|
164 |
+
// إضافة تأثيرات انتقالية للروابط
|
165 |
+
const navLinks = document.querySelectorAll('.nav-link');
|
166 |
+
if (navLinks) {
|
167 |
+
navLinks.forEach(link => {
|
168 |
+
link.addEventListener('click', function(e) {
|
169 |
+
const href = this.getAttribute('href');
|
170 |
+
|
171 |
+
// إذا كان الرابط داخلياً
|
172 |
+
if (href.startsWith('#')) {
|
173 |
+
e.preventDefault();
|
174 |
+
const targetId = href.substring(1);
|
175 |
+
const targetElement = document.getElementById(targetId);
|
176 |
+
|
177 |
+
if (targetElement) {
|
178 |
+
window.scrollTo({
|
179 |
+
top: targetElement.offsetTop - 100,
|
180 |
+
behavior: 'smooth'
|
181 |
+
});
|
182 |
+
}
|
183 |
+
}
|
184 |
+
});
|
185 |
+
});
|
186 |
+
}
|
187 |
+
|
188 |
+
// تمييز الرابط النشط
|
189 |
+
highlightActiveLink();
|
190 |
+
}
|
191 |
+
|
192 |
+
// تمييز الرابط النشط
|
193 |
+
function highlightActiveLink() {
|
194 |
+
const currentPath = window.location.pathname;
|
195 |
+
const navLinks = document.querySelectorAll('.nav-link');
|
196 |
+
|
197 |
+
if (navLinks) {
|
198 |
+
navLinks.forEach(link => {
|
199 |
+
const href = link.getAttribute('href');
|
200 |
+
if (href === currentPath || (currentPath === '/' && href === '/index.html')) {
|
201 |
+
link.classList.add('active');
|
202 |
+
}
|
203 |
+
});
|
204 |
+
}
|
205 |
+
}
|
206 |
+
|
207 |
+
// دالة لعرض رسائل النجاح
|
208 |
+
function showSuccessMessage(message, duration = 3000) {
|
209 |
+
showMessage(message, 'success', duration);
|
210 |
+
}
|
211 |
+
|
212 |
+
// دالة لعرض رسائل الخطأ
|
213 |
+
function showErrorMessage(message, duration = 3000) {
|
214 |
+
showMessage(message, 'error', duration);
|
215 |
+
}
|
216 |
+
|
217 |
+
// دالة لعرض رسائل التحذير
|
218 |
+
function showWarningMessage(message, duration = 3000) {
|
219 |
+
showMessage(message, 'warning', duration);
|
220 |
+
}
|
221 |
+
|
222 |
+
// دالة لعرض رسائل المعلومات
|
223 |
+
function showInfoMessage(message, duration = 3000) {
|
224 |
+
showMessage(message, 'info', duration);
|
225 |
+
}
|
226 |
+
|
227 |
+
// دالة عامة لعرض الرسائل
|
228 |
+
function showMessage(message, type, duration) {
|
229 |
+
// إنشاء عنصر الرسالة
|
230 |
+
const messageElement = document.createElement('div');
|
231 |
+
messageElement.className = `alert alert-${type}`;
|
232 |
+
messageElement.innerHTML = `
|
233 |
+
<span class="message">${message}</span>
|
234 |
+
<button class="close">×</button>
|
235 |
+
`;
|
236 |
+
|
237 |
+
// إضافة العنصر إلى الصفحة
|
238 |
+
const alertContainer = document.querySelector('.alert-container');
|
239 |
+
if (alertContainer) {
|
240 |
+
alertContainer.appendChild(messageElement);
|
241 |
+
} else {
|
242 |
+
const container = document.createElement('div');
|
243 |
+
container.className = 'alert-container';
|
244 |
+
container.appendChild(messageElement);
|
245 |
+
document.body.appendChild(container);
|
246 |
+
}
|
247 |
+
|
248 |
+
// إضافة مستمع لزر الإغلاق
|
249 |
+
const closeButton = messageElement.querySelector('.close');
|
250 |
+
if (closeButton) {
|
251 |
+
closeButton.addEventListener('click', function() {
|
252 |
+
messageElement.remove();
|
253 |
+
});
|
254 |
+
}
|
255 |
+
|
256 |
+
// إزالة الرسالة تلقائياً بعد المدة المحددة
|
257 |
+
if (duration > 0) {
|
258 |
+
setTimeout(function() {
|
259 |
+
messageElement.classList.add('fade-out');
|
260 |
+
setTimeout(function() {
|
261 |
+
messageElement.remove();
|
262 |
+
}, 300);
|
263 |
+
}, duration);
|
264 |
+
}
|
265 |
+
}
|
266 |
+
|
267 |
+
// دالة للتحقق من صحة النموذج
|
268 |
+
function validateForm(formId) {
|
269 |
+
const form = document.getElementById(formId);
|
270 |
+
if (!form) return false;
|
271 |
+
|
272 |
+
let isValid = true;
|
273 |
+
const requiredFields = form.querySelectorAll('[required]');
|
274 |
+
|
275 |
+
requiredFields.forEach(field => {
|
276 |
+
if (!field.value.trim()) {
|
277 |
+
isValid = false;
|
278 |
+
field.classList.add('error');
|
279 |
+
|
280 |
+
// إضافة رسالة خطأ
|
281 |
+
const errorMessage = field.dataset.errorMessage || 'هذا الحقل مطلوب';
|
282 |
+
let errorElement = field.nextElementSibling;
|
283 |
+
|
284 |
+
if (!errorElement || !errorElement.classList.contains('error-message')) {
|
285 |
+
errorElement = document.createElement('div');
|
286 |
+
errorElement.className = 'error-message';
|
287 |
+
field.parentNode.insertBefore(errorElement, field.nextSibling);
|
288 |
+
}
|
289 |
+
|
290 |
+
errorElement.textContent = errorMessage;
|
291 |
+
} else {
|
292 |
+
field.classList.remove('error');
|
293 |
+
const errorElement = field.nextElementSibling;
|
294 |
+
if (errorElement && errorElement.classList.contains('error-message')) {
|
295 |
+
errorElement.remove();
|
296 |
+
}
|
297 |
+
}
|
298 |
+
});
|
299 |
+
|
300 |
+
return isValid;
|
301 |
+
}
|
302 |
+
|
303 |
+
// تصدير الدوال للاستخدام العام
|
304 |
+
window.app = {
|
305 |
+
showSuccessMessage,
|
306 |
+
showErrorMessage,
|
307 |
+
showWarningMessage,
|
308 |
+
showInfoMessage,
|
309 |
+
validateForm
|
310 |
+
};
|
templates/base.html
ADDED
@@ -0,0 +1,318 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="ar" dir="rtl">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>نظام تحليل المناقصات</title>
|
7 |
+
<!-- Font Awesome -->
|
8 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
9 |
+
<!-- نظام التصميم الموحد -->
|
10 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/unified_design_system.css') }}">
|
11 |
+
<!-- تخصيصات إضافية -->
|
12 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
13 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
14 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.28/jspdf.plugin.autotable.min.js"></script>
|
15 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
|
16 |
+
</head>
|
17 |
+
<body>
|
18 |
+
<div class="app-container">
|
19 |
+
<!-- الشريط الجانبي -->
|
20 |
+
<aside class="sidebar">
|
21 |
+
<div class="sidebar-header">
|
22 |
+
<div class="sidebar-logo">
|
23 |
+
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="شعار النظام">
|
24 |
+
</div>
|
25 |
+
<button class="sidebar-toggle">
|
26 |
+
<i class="fas fa-bars"></i>
|
27 |
+
</button>
|
28 |
+
</div>
|
29 |
+
<nav class="sidebar-menu">
|
30 |
+
<div class="menu-section">
|
31 |
+
<div class="menu-section-title">القائمة الرئيسية</div>
|
32 |
+
<a href="{{ url_for('index') }}" class="menu-item active">
|
33 |
+
<i class="fas fa-home menu-icon"></i>
|
34 |
+
<span>الرئيسية</span>
|
35 |
+
</a>
|
36 |
+
<a href="{{ url_for('tenders') }}" class="menu-item">
|
37 |
+
<i class="fas fa-file-contract menu-icon"></i>
|
38 |
+
<span>المناقصات</span>
|
39 |
+
</a>
|
40 |
+
<a href="{{ url_for('analysis') }}" class="menu-item">
|
41 |
+
<i class="fas fa-chart-line menu-icon"></i>
|
42 |
+
<span>التحليلات</span>
|
43 |
+
</a>
|
44 |
+
<a href="{{ url_for('reports') }}" class="menu-item">
|
45 |
+
<i class="fas fa-file-alt menu-icon"></i>
|
46 |
+
<span>التقارير</span>
|
47 |
+
</a>
|
48 |
+
</div>
|
49 |
+
<div class="menu-section">
|
50 |
+
<div class="menu-section-title">الأدوات</div>
|
51 |
+
<a href="{{ url_for('ai_assistant') }}" class="menu-item">
|
52 |
+
<i class="fas fa-robot menu-icon"></i>
|
53 |
+
<span>المساعد الذكي</span>
|
54 |
+
</a>
|
55 |
+
<a href="{{ url_for('document_extraction') }}" class="menu-item">
|
56 |
+
<i class="fas fa-file-import menu-icon"></i>
|
57 |
+
<span>استخراج المستندات</span>
|
58 |
+
</a>
|
59 |
+
<a href="{{ url_for('settings') }}" class="menu-item">
|
60 |
+
<i class="fas fa-cog menu-icon"></i>
|
61 |
+
<span>الإعدادات</span>
|
62 |
+
</a>
|
63 |
+
</div>
|
64 |
+
</nav>
|
65 |
+
</aside>
|
66 |
+
|
67 |
+
<!-- المحتوى الرئيسي -->
|
68 |
+
<main class="main-content">
|
69 |
+
<!-- ترويسة الصفحة -->
|
70 |
+
<header class="header">
|
71 |
+
<div class="d-flex align-items-center">
|
72 |
+
<button class="mobile-menu-toggle d-lg-none">
|
73 |
+
<i class="fas fa-bars"></i>
|
74 |
+
</button>
|
75 |
+
<h1 class="mb-0 mr-3">نظام تحليل المناقصات</h1>
|
76 |
+
</div>
|
77 |
+
<div class="d-flex align-items-center">
|
78 |
+
<button class="btn btn-sm btn-outline theme-toggle" data-tooltip="تبديل السمة">
|
79 |
+
<i class="fas fa-moon"></i>
|
80 |
+
</button>
|
81 |
+
<div class="dropdown ml-3">
|
82 |
+
<button class="btn btn-sm btn-outline">
|
83 |
+
<i class="fas fa-user mr-1"></i>
|
84 |
+
<span>المستخدم</span>
|
85 |
+
</button>
|
86 |
+
</div>
|
87 |
+
</div>
|
88 |
+
</header>
|
89 |
+
|
90 |
+
<!-- محتوى الصفحة -->
|
91 |
+
<div class="content-wrapper">
|
92 |
+
<!-- ترويسة المحتوى -->
|
93 |
+
<div class="header-container">
|
94 |
+
<div class="header-title">
|
95 |
+
<h1>لوحة التحكم</h1>
|
96 |
+
<p>مرحبًا بك في نظام تحليل المناقصات</p>
|
97 |
+
</div>
|
98 |
+
<div class="header-info">
|
99 |
+
<div class="date-box">
|
100 |
+
<div class="date-day">{{ day }}</div>
|
101 |
+
<div class="date-info">
|
102 |
+
<div class="date-month">{{ month }}</div>
|
103 |
+
<div class="date-year">{{ year }}</div>
|
104 |
+
</div>
|
105 |
+
</div>
|
106 |
+
</div>
|
107 |
+
</div>
|
108 |
+
|
109 |
+
<!-- إحصائيات لوحة التحكم -->
|
110 |
+
<div class="dashboard-stats">
|
111 |
+
<div class="stat-card">
|
112 |
+
<div class="stat-icon"><i class="fas fa-file-contract"></i></div>
|
113 |
+
<div class="stat-value">156</div>
|
114 |
+
<div class="stat-label">إجمالي المناقصات</div>
|
115 |
+
<div class="stat-change positive"><i class="fas fa-arrow-up"></i> 12% منذ الشهر الماضي</div>
|
116 |
+
</div>
|
117 |
+
<div class="stat-card">
|
118 |
+
<div class="stat-icon"><i class="fas fa-check-circle"></i></div>
|
119 |
+
<div class="stat-value">89</div>
|
120 |
+
<div class="stat-label">مناقصات مكتملة</div>
|
121 |
+
<div class="stat-change positive"><i class="fas fa-arrow-up"></i> 8% منذ الشهر الماضي</div>
|
122 |
+
</div>
|
123 |
+
<div class="stat-card">
|
124 |
+
<div class="stat-icon"><i class="fas fa-spinner"></i></div>
|
125 |
+
<div class="stat-value">42</div>
|
126 |
+
<div class="stat-label">قيد التحليل</div>
|
127 |
+
<div class="stat-change negative"><i class="fas fa-arrow-down"></i> 5% منذ الشهر الماضي</div>
|
128 |
+
</div>
|
129 |
+
<div class="stat-card">
|
130 |
+
<div class="stat-icon"><i class="fas fa-money-bill-wave"></i></div>
|
131 |
+
<div class="stat-value">25.4M</div>
|
132 |
+
<div class="stat-label">إجمالي القيمة (ريال)</div>
|
133 |
+
<div class="stat-change positive"><i class="fas fa-arrow-up"></i> 15% منذ الشهر الماضي</div>
|
134 |
+
</div>
|
135 |
+
</div>
|
136 |
+
|
137 |
+
<!-- المناقصات الأخيرة -->
|
138 |
+
<div class="card mb-4">
|
139 |
+
<div class="card-header">
|
140 |
+
<h3 class="card-title">المناقصات الأخيرة</h3>
|
141 |
+
<button class="btn btn-primary btn-sm">عرض الكل</button>
|
142 |
+
</div>
|
143 |
+
<div class="card-body">
|
144 |
+
<div class="table-container">
|
145 |
+
<table class="table table-striped">
|
146 |
+
<thead>
|
147 |
+
<tr>
|
148 |
+
<th>#</th>
|
149 |
+
<th>اسم المناقصة</th>
|
150 |
+
<th>الجهة</th>
|
151 |
+
<th>التاريخ</th>
|
152 |
+
<th>الحالة</th>
|
153 |
+
<th>الإجراءات</th>
|
154 |
+
</tr>
|
155 |
+
</thead>
|
156 |
+
<tbody>
|
157 |
+
<tr>
|
158 |
+
<td>1</td>
|
159 |
+
<td>مشروع تطوير البنية التحتية</td>
|
160 |
+
<td>وزارة النقل</td>
|
161 |
+
<td>2025/03/15</td>
|
162 |
+
<td><span class="badge badge-success">مكتملة</span></td>
|
163 |
+
<td>
|
164 |
+
<button class="btn btn-primary btn-sm"><i class="fas fa-eye"></i></button>
|
165 |
+
<button class="btn btn-secondary btn-sm"><i class="fas fa-edit"></i></button>
|
166 |
+
</td>
|
167 |
+
</tr>
|
168 |
+
<tr>
|
169 |
+
<td>2</td>
|
170 |
+
<td>توريد معدات طبية</td>
|
171 |
+
<td>وزارة الصحة</td>
|
172 |
+
<td>2025/03/20</td>
|
173 |
+
<td><span class="badge badge-warning">قيد التحليل</span></td>
|
174 |
+
<td>
|
175 |
+
<button class="btn btn-primary btn-sm"><i class="fas fa-eye"></i></button>
|
176 |
+
<button class="btn btn-secondary btn-sm"><i class="fas fa-edit"></i></button>
|
177 |
+
</td>
|
178 |
+
</tr>
|
179 |
+
<tr>
|
180 |
+
<td>3</td>
|
181 |
+
<td>بناء مدرسة ثانوية</td>
|
182 |
+
<td>وزارة التعليم</td>
|
183 |
+
<td>2025/03/25</td>
|
184 |
+
<td><span class="badge badge-info">جديدة</span></td>
|
185 |
+
<td>
|
186 |
+
<button class="btn btn-primary btn-sm"><i class="fas fa-eye"></i></button>
|
187 |
+
<button class="btn btn-secondary btn-sm"><i class="fas fa-edit"></i></button>
|
188 |
+
</td>
|
189 |
+
</tr>
|
190 |
+
</tbody>
|
191 |
+
</table>
|
192 |
+
</div>
|
193 |
+
</div>
|
194 |
+
</div>
|
195 |
+
|
196 |
+
<!-- تحليلات المناقصات -->
|
197 |
+
<div class="row">
|
198 |
+
<div class="col-md-6">
|
199 |
+
<div class="card mb-4">
|
200 |
+
<div class="card-header">
|
201 |
+
<h3 class="card-title">توزيع المناقصات حسب الجهة</h3>
|
202 |
+
</div>
|
203 |
+
<div class="card-body">
|
204 |
+
<canvas id="entityChart" height="300"></canvas>
|
205 |
+
</div>
|
206 |
+
</div>
|
207 |
+
</div>
|
208 |
+
<div class="col-md-6">
|
209 |
+
<div class="card mb-4">
|
210 |
+
<div class="card-header">
|
211 |
+
<h3 class="card-title">توزيع المناقصات حسب الحالة</h3>
|
212 |
+
</div>
|
213 |
+
<div class="card-body">
|
214 |
+
<canvas id="statusChart" height="300"></canvas>
|
215 |
+
</div>
|
216 |
+
</div>
|
217 |
+
</div>
|
218 |
+
</div>
|
219 |
+
|
220 |
+
<!-- الابتكارات والميزات -->
|
221 |
+
<h2 class="module-title">الابتكارات والميزات</h2>
|
222 |
+
<div class="row">
|
223 |
+
<div class="col-md-4">
|
224 |
+
<div class="innovation-card">
|
225 |
+
<div class="innovation-icon"><i class="fas fa-robot"></i></div>
|
226 |
+
<h3>المساعد الذكي</h3>
|
227 |
+
<p>استخدم الذكاء الاصطناعي لتحليل المناقصات واستخراج المعلومات المهمة بدقة عالية.</p>
|
228 |
+
</div>
|
229 |
+
</div>
|
230 |
+
<div class="col-md-4">
|
231 |
+
<div class="innovation-card">
|
232 |
+
<div class="innovation-icon"><i class="fas fa-file-import"></i></div>
|
233 |
+
<h3>استخراج المستندات</h3>
|
234 |
+
<p>استخرج البيانات من مستندات المناقصات بتنسيقات مختلفة (PDF، Word، Excel) بشكل آلي.</p>
|
235 |
+
</div>
|
236 |
+
</div>
|
237 |
+
<div class="col-md-4">
|
238 |
+
<div class="innovation-card">
|
239 |
+
<div class="innovation-icon"><i class="fas fa-chart-line"></i></div>
|
240 |
+
<h3>تحليلات متقدمة</h3>
|
241 |
+
<p>احصل على تحليلات متقدمة ورؤى قيمة حول المناقصات باستخدام تقنيات تحليل البيانات.</p>
|
242 |
+
</div>
|
243 |
+
</div>
|
244 |
+
</div>
|
245 |
+
</div>
|
246 |
+
|
247 |
+
<!-- تذييل الصفحة -->
|
248 |
+
<footer class="footer">
|
249 |
+
<p>جميع الحقوق محفوظة © 2025 نظام تحليل المناقصات</p>
|
250 |
+
</footer>
|
251 |
+
</main>
|
252 |
+
</div>
|
253 |
+
|
254 |
+
<!-- سكريبت JavaScript المحسن -->
|
255 |
+
<script src="{{ url_for('static', filename='js/enhanced_main.js') }}"></script>
|
256 |
+
<script>
|
257 |
+
// بيانات الرسوم البيانية
|
258 |
+
document.addEventListener('DOMContentLoaded', function() {
|
259 |
+
// رسم بياني لتوزيع المناقصات حسب الجهة
|
260 |
+
const entityCtx = document.getElementById('entityChart').getContext('2d');
|
261 |
+
new Chart(entityCtx, {
|
262 |
+
type: 'pie',
|
263 |
+
data: {
|
264 |
+
labels: ['وزارة النقل', 'وزارة الصحة', 'وزارة التعليم', 'وزارة الإسكان', 'أخرى'],
|
265 |
+
datasets: [{
|
266 |
+
data: [30, 25, 20, 15, 10],
|
267 |
+
backgroundColor: [
|
268 |
+
'#2563eb', // primary-color
|
269 |
+
'#10b981', // secondary-color
|
270 |
+
'#f59e0b', // accent-color
|
271 |
+
'#3b82f6', // info-color
|
272 |
+
'#94a3b8' // text-light
|
273 |
+
],
|
274 |
+
borderWidth: 1
|
275 |
+
}]
|
276 |
+
},
|
277 |
+
options: {
|
278 |
+
responsive: true,
|
279 |
+
plugins: {
|
280 |
+
legend: {
|
281 |
+
position: 'right',
|
282 |
+
rtl: true
|
283 |
+
}
|
284 |
+
}
|
285 |
+
}
|
286 |
+
});
|
287 |
+
|
288 |
+
// رسم بياني لتوزيع المناقصات حسب الحالة
|
289 |
+
const statusCtx = document.getElementById('statusChart').getContext('2d');
|
290 |
+
new Chart(statusCtx, {
|
291 |
+
type: 'doughnut',
|
292 |
+
data: {
|
293 |
+
labels: ['مكتملة', 'قيد التحليل', 'جديدة', 'ملغاة'],
|
294 |
+
datasets: [{
|
295 |
+
data: [45, 30, 20, 5],
|
296 |
+
backgroundColor: [
|
297 |
+
'#22c55e', // success-color
|
298 |
+
'#f59e0b', // warning-color
|
299 |
+
'#3b82f6', // info-color
|
300 |
+
'#ef4444' // danger-color
|
301 |
+
],
|
302 |
+
borderWidth: 1
|
303 |
+
}]
|
304 |
+
},
|
305 |
+
options: {
|
306 |
+
responsive: true,
|
307 |
+
plugins: {
|
308 |
+
legend: {
|
309 |
+
position: 'right',
|
310 |
+
rtl: true
|
311 |
+
}
|
312 |
+
}
|
313 |
+
}
|
314 |
+
});
|
315 |
+
});
|
316 |
+
</script>
|
317 |
+
</body>
|
318 |
+
</html>
|
utils/components/header.py
CHANGED
@@ -7,26 +7,18 @@ from datetime import datetime
|
|
7 |
import config
|
8 |
|
9 |
|
10 |
-
def render_header(
|
11 |
"""
|
12 |
عرض ترويسة الصفحة المحسنة
|
13 |
-
|
14 |
-
الوسيطات:
|
15 |
-
page_title: عنوان الصفحة المعروضة (اختياري)
|
16 |
"""
|
17 |
# إنشاء مكون الترويسة باستخدام HTML
|
18 |
-
title_display = "نظام تحليل العقود والمناقصات"
|
19 |
-
# إذا تم تمرير عنوان للصفحة، قم بإضافته للعنوان الرئيسي
|
20 |
-
if page_title:
|
21 |
-
title_display = f"نظام تحليل العقود والمناقصات: {page_title}"
|
22 |
-
|
23 |
header_html = """
|
24 |
<div class="header-container">
|
25 |
<div class="header-title">
|
26 |
<div class="logo">
|
27 |
<span class="logo-text">WAHBi AI</span>
|
28 |
</div>
|
29 |
-
<h1
|
30 |
<p>الحلول الشاملة للتسعير والتحليل بالذكاء الاصطناعي - شركة شبه الجزيرة للمقاولات</p>
|
31 |
</div>
|
32 |
<div class="header-info">
|
@@ -52,7 +44,7 @@ def render_header(page_title=None):
|
|
52 |
year = today.year
|
53 |
|
54 |
# استبدال القيم في قالب HTML
|
55 |
-
header_html = header_html.format(
|
56 |
|
57 |
# عرض الترويسة
|
58 |
st.markdown(header_html, unsafe_allow_html=True)
|
|
|
7 |
import config
|
8 |
|
9 |
|
10 |
+
def render_header():
|
11 |
"""
|
12 |
عرض ترويسة الصفحة المحسنة
|
|
|
|
|
|
|
13 |
"""
|
14 |
# إنشاء مكون الترويسة باستخدام HTML
|
|
|
|
|
|
|
|
|
|
|
15 |
header_html = """
|
16 |
<div class="header-container">
|
17 |
<div class="header-title">
|
18 |
<div class="logo">
|
19 |
<span class="logo-text">WAHBi AI</span>
|
20 |
</div>
|
21 |
+
<h1>نظام تحليل العقود والمناقصات</h1>
|
22 |
<p>الحلول الشاملة للتسعير والتحليل بالذكاء الاصطناعي - شركة شبه الجزيرة للمقاولات</p>
|
23 |
</div>
|
24 |
<div class="header-info">
|
|
|
44 |
year = today.year
|
45 |
|
46 |
# استبدال القيم في قالب HTML
|
47 |
+
header_html = header_html.format(day=day, month=month, year=year)
|
48 |
|
49 |
# عرض الترويسة
|
50 |
st.markdown(header_html, unsafe_allow_html=True)
|
utils/components/sidebar.py
CHANGED
@@ -59,14 +59,8 @@ def render_sidebar():
|
|
59 |
default_index=0,
|
60 |
styles={
|
61 |
"container": {"padding": "5px", "background-color": "#f0f2f6", "direction": "rtl"},
|
62 |
-
"icon": {"color": "orange", "font-size": "18px"
|
63 |
-
"nav-link": {
|
64 |
-
"font-size": "14px",
|
65 |
-
"text-align": "right",
|
66 |
-
"margin": "0px",
|
67 |
-
"direction": "rtl",
|
68 |
-
"justify-content": "flex-end"
|
69 |
-
},
|
70 |
"nav-link-selected": {"background-color": "#ff9a3c"},
|
71 |
}
|
72 |
)
|
|
|
59 |
default_index=0,
|
60 |
styles={
|
61 |
"container": {"padding": "5px", "background-color": "#f0f2f6", "direction": "rtl"},
|
62 |
+
"icon": {"color": "orange", "font-size": "18px"},
|
63 |
+
"nav-link": {"font-size": "14px", "text-align": "right", "margin": "0px"},
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
"nav-link-selected": {"background-color": "#ff9a3c"},
|
65 |
}
|
66 |
)
|
utils/helpers.py
CHANGED
@@ -2,281 +2,331 @@
|
|
2 |
# -*- coding: utf-8 -*-
|
3 |
|
4 |
"""
|
5 |
-
وحدة
|
6 |
-
|
7 |
"""
|
8 |
|
9 |
import os
|
10 |
-
import
|
|
|
|
|
|
|
11 |
import json
|
12 |
import re
|
13 |
-
import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
def create_directory_if_not_exists(directory_path):
|
16 |
-
"""إنشاء
|
17 |
-
|
18 |
-
os.
|
|
|
|
|
19 |
return True
|
20 |
-
|
|
|
|
|
|
|
21 |
|
22 |
def get_data_folder():
|
23 |
-
"""الحصول على مسار مجلد البيانات"""
|
24 |
-
|
|
|
25 |
create_directory_if_not_exists(data_folder)
|
26 |
return data_folder
|
27 |
|
28 |
-
|
29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
if timestamp is None:
|
31 |
-
timestamp = datetime.
|
32 |
-
|
|
|
|
|
|
|
|
|
33 |
|
34 |
def get_user_info():
|
35 |
"""الحصول على معلومات المستخدم الحالي"""
|
36 |
-
# في
|
37 |
-
|
|
|
|
|
|
|
38 |
return {
|
39 |
"id": 1,
|
40 |
"username": "admin",
|
41 |
-
"
|
42 |
-
"
|
|
|
|
|
|
|
43 |
}
|
44 |
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
color: #1E88E5;
|
51 |
-
text-align: center;
|
52 |
-
margin-bottom: 20px;
|
53 |
-
font-weight: bold;
|
54 |
-
}
|
55 |
-
|
56 |
-
.section-title {
|
57 |
-
color: #1565C0;
|
58 |
-
margin-top: 20px;
|
59 |
-
margin-bottom: 10px;
|
60 |
-
font-weight: bold;
|
61 |
-
}
|
62 |
-
|
63 |
-
.info-box {
|
64 |
-
background-color: #E3F2FD;
|
65 |
-
padding: 15px;
|
66 |
-
border-radius: 5px;
|
67 |
-
margin-bottom: 15px;
|
68 |
-
}
|
69 |
-
|
70 |
-
.warning-box {
|
71 |
-
background-color: #FFF8E1;
|
72 |
-
padding: 15px;
|
73 |
-
border-radius: 5px;
|
74 |
-
margin-bottom: 15px;
|
75 |
-
}
|
76 |
-
|
77 |
-
.error-box {
|
78 |
-
background-color: #FFEBEE;
|
79 |
-
padding: 15px;
|
80 |
-
border-radius: 5px;
|
81 |
-
margin-bottom: 15px;
|
82 |
-
}
|
83 |
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
|
|
|
|
90 |
|
91 |
-
|
92 |
-
|
93 |
-
|
|
|
94 |
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
|
|
|
|
|
|
|
|
|
|
101 |
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
106 |
|
107 |
-
|
108 |
-
|
109 |
-
color: white !important;
|
110 |
-
}
|
111 |
|
112 |
-
|
113 |
-
|
114 |
-
}
|
115 |
|
116 |
-
|
117 |
-
|
118 |
-
}
|
119 |
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
border: 1px solid #ddd !important;
|
124 |
-
padding: 10px !important;
|
125 |
-
}
|
126 |
|
127 |
-
|
128 |
-
|
129 |
-
}
|
130 |
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
}
|
135 |
-
</style>
|
136 |
|
137 |
-
|
138 |
-
""", unsafe_allow_html=True)
|
139 |
-
|
140 |
-
def render_credits():
|
141 |
-
"""عرض المعلوم��ت عن حقوق الملكية وإصدار النظام"""
|
142 |
-
st.markdown("""
|
143 |
-
<div class="footer">
|
144 |
-
<p>هذا النظام يعمل لصالح شركة شبه الجزيرة للمقاولات، جميع الحقوق محفوظة 2025</p>
|
145 |
-
<p>نظام WAHBi AI - الإصدار 2.0</p>
|
146 |
-
</div>
|
147 |
-
""", unsafe_allow_html=True)
|
148 |
|
149 |
-
def load_icons():
|
150 |
-
"""تحميل الأيقونات المستخدمة في النظام"""
|
151 |
-
icons = {
|
152 |
-
"project": "🏗️",
|
153 |
-
"document": "📄",
|
154 |
-
"analysis": "🔍",
|
155 |
-
"warning": "⚠️",
|
156 |
-
"success": "✅",
|
157 |
-
"error": "❌",
|
158 |
-
"info": "ℹ️",
|
159 |
-
"settings": "⚙️",
|
160 |
-
"user": "👤",
|
161 |
-
"money": "💰",
|
162 |
-
"time": "⏱️",
|
163 |
-
"location": "📍",
|
164 |
-
"notification": "🔔",
|
165 |
-
"edit": "✏️",
|
166 |
-
"delete": "🗑️",
|
167 |
-
"upload": "📤",
|
168 |
-
"download": "📥",
|
169 |
-
"save": "💾",
|
170 |
-
"cancel": "❌",
|
171 |
-
"add": "➕",
|
172 |
-
"calendar": "📅",
|
173 |
-
"chat": "💬",
|
174 |
-
"search": "🔎",
|
175 |
-
"star": "⭐",
|
176 |
-
"trophy": "🏆",
|
177 |
-
"medal": "🥇",
|
178 |
-
"chart": "📊",
|
179 |
-
"map": "🗺️",
|
180 |
-
"building": "🏢",
|
181 |
-
"road": "🛣️",
|
182 |
-
"bridge": "🌉",
|
183 |
-
}
|
184 |
-
return icons
|
185 |
|
186 |
-
def
|
187 |
-
"""
|
188 |
-
|
189 |
-
if decimal_places == 0:
|
190 |
-
return "{:,.0f}".format(number)
|
191 |
-
else:
|
192 |
-
return "{:,.{dp}f}".format(number, dp=decimal_places)
|
193 |
-
return str(number)
|
194 |
|
195 |
-
def format_currency(amount, currency="ريال", decimal_places=2):
|
196 |
-
"""تنسيق المبالغ المالية"""
|
197 |
-
if amount is None:
|
198 |
-
return "غير محدد"
|
199 |
-
formatted = format_number(amount, decimal_places)
|
200 |
-
return f"{formatted} {currency}"
|
201 |
|
202 |
-
def
|
203 |
-
"""
|
204 |
-
|
205 |
-
:param label: نص الزر
|
206 |
-
:param key: مفتاح الزر الفريد
|
207 |
-
:param type: نوع التنسيق ('primary', 'secondary', 'success', 'warning', 'danger', 'info', 'glass', 'flat')
|
208 |
-
:param on_click: الدالة التي سيتم تنفيذها عند النقر
|
209 |
-
:param args: معاملات الدالة
|
210 |
-
:param full_width: هل يأخذ الزر العرض كاملاً
|
211 |
-
:param icon: أيقونة لعرضها قبل النص (emoji أو HTML)
|
212 |
-
:param is_link: إذا كان الزر رابطاً بدلاً من زر عادي
|
213 |
-
:param help: نص المساعدة للزر
|
214 |
-
:return: زر مُنسّق
|
215 |
-
"""
|
216 |
-
if is_link:
|
217 |
-
btn_class = f"{type}-btn"
|
218 |
-
if icon:
|
219 |
-
btn_class += " action-btn"
|
220 |
-
label_with_icon = f"{icon} {label}"
|
221 |
-
else:
|
222 |
-
label_with_icon = label
|
223 |
-
|
224 |
-
button_html = f"""
|
225 |
-
<div class="{btn_class}">
|
226 |
-
{label_with_icon}
|
227 |
-
</div>
|
228 |
-
"""
|
229 |
-
return st.markdown(button_html, unsafe_allow_html=True)
|
230 |
-
else:
|
231 |
-
with st.container():
|
232 |
-
btn_class = f"{type}-btn"
|
233 |
-
if icon:
|
234 |
-
btn_class += " action-btn"
|
235 |
-
label_with_icon = f"{icon} {label}"
|
236 |
-
else:
|
237 |
-
label_with_icon = label
|
238 |
-
|
239 |
-
st.markdown(f'<div class="{btn_class}">', unsafe_allow_html=True)
|
240 |
-
clicked = st.button(label_with_icon, key=key, on_click=on_click, args=args, use_container_width=full_width, help=help)
|
241 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
242 |
-
return clicked
|
243 |
-
|
244 |
-
def filter_dataframe(df, column, value):
|
245 |
-
"""ترشيح إطار البيانات"""
|
246 |
-
if value == "الكل":
|
247 |
-
return df
|
248 |
-
return df[df[column] == value]
|
249 |
-
|
250 |
-
def get_file_extension(filename):
|
251 |
-
"""استخراج امتداد الملف"""
|
252 |
-
if not filename:
|
253 |
return ""
|
254 |
-
|
|
|
|
|
|
|
255 |
|
256 |
-
def
|
257 |
-
"""
|
|
|
258 |
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
Returns:
|
263 |
-
list: قائمة بالأرقام المستخرجة
|
264 |
-
"""
|
265 |
-
if not text:
|
266 |
-
return []
|
267 |
|
268 |
-
|
269 |
-
pattern = r'[-+]?\d*\.\d+|\d+'
|
270 |
|
271 |
-
|
272 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
273 |
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
279 |
else:
|
280 |
-
|
281 |
-
|
282 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
# -*- coding: utf-8 -*-
|
3 |
|
4 |
"""
|
5 |
+
وحدة المساعدة المركزية للنظام
|
6 |
+
تحتوي على دوال مساعدة مشتركة تستخدم في جميع أنحاء التطبيق
|
7 |
"""
|
8 |
|
9 |
import os
|
10 |
+
import sys
|
11 |
+
import streamlit as st
|
12 |
+
import pandas as pd
|
13 |
+
import numpy as np
|
14 |
import json
|
15 |
import re
|
16 |
+
import time
|
17 |
+
import tempfile
|
18 |
+
from datetime import datetime, timedelta
|
19 |
+
import random
|
20 |
+
import secrets
|
21 |
+
import shutil
|
22 |
+
import base64
|
23 |
+
import logging
|
24 |
+
from pathlib import Path
|
25 |
+
|
26 |
+
# تكوين التسجيلات
|
27 |
+
logging.basicConfig(
|
28 |
+
level=logging.INFO,
|
29 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
30 |
+
)
|
31 |
+
logger = logging.getLogger("wahbi-ai")
|
32 |
+
|
33 |
|
34 |
def create_directory_if_not_exists(directory_path):
|
35 |
+
"""إنشاء مسار إذا لم يكن موجوداً"""
|
36 |
+
try:
|
37 |
+
if not os.path.exists(directory_path):
|
38 |
+
os.makedirs(directory_path)
|
39 |
+
logger.info(f"تم إنشاء المجلد: {directory_path}")
|
40 |
return True
|
41 |
+
except Exception as e:
|
42 |
+
logger.error(f"خطأ في إنشاء المجلد {directory_path}: {e}")
|
43 |
+
return False
|
44 |
+
|
45 |
|
46 |
def get_data_folder():
|
47 |
+
"""الحصول على مسار مجلد البيانات الرئيسي"""
|
48 |
+
# مسار بيانات النظام الرئيسي
|
49 |
+
data_folder = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data")
|
50 |
create_directory_if_not_exists(data_folder)
|
51 |
return data_folder
|
52 |
|
53 |
+
|
54 |
+
def load_config():
|
55 |
+
"""تحميل إعدادات التكوين من ملف config.json"""
|
56 |
+
config_path = os.path.join(get_data_folder(), "config.json")
|
57 |
+
|
58 |
+
# إذا لم يكن ملف التكوين موجوداً، قم بإنشاء ملف افتراضي
|
59 |
+
if not os.path.exists(config_path):
|
60 |
+
default_config = {
|
61 |
+
"system": {
|
62 |
+
"version": "1.0.0",
|
63 |
+
"release_date": "2025-03-30",
|
64 |
+
"company_name": "شركة شبه الجزيرة للمقاولات",
|
65 |
+
"company_logo": "",
|
66 |
+
"language": "ar",
|
67 |
+
"theme": "light",
|
68 |
+
"debug_mode": False
|
69 |
+
},
|
70 |
+
"ai_models": {
|
71 |
+
"default_model": "claude-3-7-sonnet-20250219",
|
72 |
+
"openai_model": "gpt-4o",
|
73 |
+
"huggingface_model": "mistralai/Mistral-7B-Instruct-v0.2"
|
74 |
+
},
|
75 |
+
"notifications": {
|
76 |
+
"enable_email": False,
|
77 |
+
"enable_browser": True,
|
78 |
+
"check_interval": 60
|
79 |
+
}
|
80 |
+
}
|
81 |
+
|
82 |
+
with open(config_path, 'w', encoding='utf-8') as f:
|
83 |
+
json.dump(default_config, f, ensure_ascii=False, indent=2)
|
84 |
+
|
85 |
+
return default_config
|
86 |
+
|
87 |
+
# تحميل ملف التكوين الموجود
|
88 |
+
try:
|
89 |
+
with open(config_path, 'r', encoding='utf-8') as f:
|
90 |
+
return json.load(f)
|
91 |
+
except Exception as e:
|
92 |
+
logger.error(f"خطأ في تحميل ملف التكوين: {e}")
|
93 |
+
return {}
|
94 |
+
|
95 |
+
|
96 |
+
def save_config(config):
|
97 |
+
"""حفظ إعدادات التكوين إلى ملف config.json"""
|
98 |
+
config_path = os.path.join(get_data_folder(), "config.json")
|
99 |
+
|
100 |
+
try:
|
101 |
+
with open(config_path, 'w', encoding='utf-8') as f:
|
102 |
+
json.dump(config, f, ensure_ascii=False, indent=2)
|
103 |
+
return True
|
104 |
+
except Exception as e:
|
105 |
+
logger.error(f"خطأ في حفظ ملف التكوين: {e}")
|
106 |
+
return False
|
107 |
+
|
108 |
+
|
109 |
+
def format_time(timestamp=None, format_str="%Y-%m-%d %H:%M:%S"):
|
110 |
+
"""تنسيق الوقت إلى صيغة معينة"""
|
111 |
if timestamp is None:
|
112 |
+
timestamp = datetime.now()
|
113 |
+
elif isinstance(timestamp, (int, float)):
|
114 |
+
timestamp = datetime.fromtimestamp(timestamp)
|
115 |
+
|
116 |
+
return timestamp.strftime(format_str)
|
117 |
+
|
118 |
|
119 |
def get_user_info():
|
120 |
"""الحصول على معلومات المستخدم الحالي"""
|
121 |
+
# في التطبيق الفعلي، يمكن استرداد معلومات المستخدم من قاعدة البيانات أو من حالة الجلسة
|
122 |
+
if "user_info" in st.session_state:
|
123 |
+
return st.session_state.user_info
|
124 |
+
|
125 |
+
# معلومات افتراضية للتطوير
|
126 |
return {
|
127 |
"id": 1,
|
128 |
"username": "admin",
|
129 |
+
"full_name": "مدير النظام",
|
130 |
+
"email": "admin@example.com",
|
131 |
+
"role": "مدير",
|
132 |
+
"department": "الإدارة",
|
133 |
+
"last_login": format_time()
|
134 |
}
|
135 |
|
136 |
+
|
137 |
+
def get_current_project():
|
138 |
+
"""الحصول على معلومات المشروع الحالي"""
|
139 |
+
if "current_project" in st.session_state:
|
140 |
+
return st.session_state.current_project
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
141 |
|
142 |
+
# في حالة عدم وجود مشروع محدد
|
143 |
+
return None
|
144 |
+
|
145 |
+
|
146 |
+
def load_icons():
|
147 |
+
"""تحميل الأيقونات المستخدمة في النظام"""
|
148 |
+
icons_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets", "icons")
|
149 |
+
icons = {}
|
150 |
|
151 |
+
# التأكد من وجود مجلد الأيقونات
|
152 |
+
if not os.path.exists(icons_path):
|
153 |
+
create_directory_if_not_exists(icons_path)
|
154 |
+
return icons
|
155 |
|
156 |
+
# تحميل جميع الأيقونات
|
157 |
+
for icon_file in os.listdir(icons_path):
|
158 |
+
if icon_file.endswith(('.png', '.svg', '.jpg')):
|
159 |
+
icon_name = os.path.splitext(icon_file)[0]
|
160 |
+
icon_path = os.path.join(icons_path, icon_file)
|
161 |
+
|
162 |
+
try:
|
163 |
+
with open(icon_path, "rb") as f:
|
164 |
+
icons[icon_name] = base64.b64encode(f.read()).decode()
|
165 |
+
except Exception as e:
|
166 |
+
logger.error(f"خطأ في تحميل الأيقونة {icon_name}: {e}")
|
167 |
|
168 |
+
return icons
|
169 |
+
|
170 |
+
|
171 |
+
def get_random_id(length=8):
|
172 |
+
"""إنشاء معرف عشوائي بطول محدد"""
|
173 |
+
return secrets.token_hex(length // 2)
|
174 |
+
|
175 |
+
|
176 |
+
def compress_text(text, max_length=10000):
|
177 |
+
"""اختصار النص إلى حد أقصى محدد مع الحفاظ على المعنى"""
|
178 |
+
if not text or len(text) <= max_length:
|
179 |
+
return text
|
180 |
|
181 |
+
# تقسيم النص إلى جمل
|
182 |
+
sentences = re.split(r'(?<=[.!?])\s+', text)
|
|
|
|
|
183 |
|
184 |
+
# حساب متوسط طول الجملة
|
185 |
+
avg_sentence_length = len(text) / len(sentences)
|
|
|
186 |
|
187 |
+
# حساب عدد الجمل التي يمكن تضمينها
|
188 |
+
num_sentences_to_keep = int(max_length / avg_sentence_length)
|
|
|
189 |
|
190 |
+
# الاحتفاظ بالجمل الأولى والأخيرة للحفاظ على السياق
|
191 |
+
keep_first = num_sentences_to_keep // 2
|
192 |
+
keep_last = num_sentences_to_keep - keep_first
|
|
|
|
|
|
|
193 |
|
194 |
+
# دمج الجمل المختارة
|
195 |
+
compressed_text = ' '.join(sentences[:keep_first] + sentences[-keep_last:])
|
|
|
196 |
|
197 |
+
# إضافة إشارة إلى أن النص تم اختصاره
|
198 |
+
if len(compressed_text) < len(text):
|
199 |
+
compressed_text += " [...المزيد من النص تم اختصاره...]"
|
|
|
|
|
200 |
|
201 |
+
return compressed_text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
202 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
203 |
|
204 |
+
def str_to_bool(text):
|
205 |
+
"""تحويل النص إلى قيمة منطقية"""
|
206 |
+
return text.lower() in ('yes', 'true', 'y', 't', '1', 'نعم', 'صحيح')
|
|
|
|
|
|
|
|
|
|
|
207 |
|
|
|
|
|
|
|
|
|
|
|
|
|
208 |
|
209 |
+
def handle_arabic_text(text):
|
210 |
+
"""معالجة النص العربي للعرض بشكل صحيح"""
|
211 |
+
if not text:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
212 |
return ""
|
213 |
+
|
214 |
+
# إضافة علامة RTL لضمان عرض النص العربي بشكل صحيح
|
215 |
+
return f"<div dir='rtl'>{text}</div>"
|
216 |
+
|
217 |
|
218 |
+
def render_credits():
|
219 |
+
"""عرض معلومات النظام وحقوق الملكية"""
|
220 |
+
st.markdown("---")
|
221 |
|
222 |
+
config = load_config()
|
223 |
+
system_info = config.get("system", {})
|
|
|
|
|
|
|
|
|
|
|
|
|
224 |
|
225 |
+
col1, col2, col3 = st.columns([1, 2, 1])
|
|
|
226 |
|
227 |
+
with col2:
|
228 |
+
st.markdown(
|
229 |
+
f"""
|
230 |
+
<div style="text-align: center; color: #666;">
|
231 |
+
<p>{system_info.get('company_name', 'شركة شبه الجزيرة للمقاولات')}</p>
|
232 |
+
<p>الإصدار: {system_info.get('version', '1.0.0')}</p>
|
233 |
+
<p>© جميع الحقوق محفوظة 2025</p>
|
234 |
+
</div>
|
235 |
+
""",
|
236 |
+
unsafe_allow_html=True
|
237 |
+
)
|
238 |
+
|
239 |
+
|
240 |
+
# دالة للحصول على اتصال قاعدة البيانات
|
241 |
+
def get_connection():
|
242 |
+
"""
|
243 |
+
دالة للحصول على اتصال بقاعدة البيانات
|
244 |
|
245 |
+
ملاحظة: ينبغي أن تكون مستبدلة بالدالة من db_connector.py في البيئة الإنتاجية
|
246 |
+
"""
|
247 |
+
try:
|
248 |
+
# استيراد المستوى الفعلي للاتصال بقاعدة البيانات
|
249 |
+
from database.db_connector import get_connection as get_db_connection
|
250 |
+
return get_db_connection()
|
251 |
+
except ImportError:
|
252 |
+
# إذا كان الاتصال بقاعدة البيانات غير متاح
|
253 |
+
logger.warning("لم يتم العثور على وحدة اتصال قاعدة البيانات. استخدام مخزن بيانات مؤقت.")
|
254 |
+
# إرجاع None للإشارة إلى عدم وجود اتصال
|
255 |
+
return None
|
256 |
+
|
257 |
+
|
258 |
+
def load_css(file_name=None):
|
259 |
+
"""تحميل ملف CSS مخصص"""
|
260 |
+
try:
|
261 |
+
if file_name:
|
262 |
+
css_file = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets", "css", file_name)
|
263 |
+
|
264 |
+
if os.path.exists(css_file):
|
265 |
+
with open(css_file, "r", encoding="utf-8") as f:
|
266 |
+
css_content = f.read()
|
267 |
+
else:
|
268 |
+
logger.warning(f"ملف CSS غير موجود: {css_file}")
|
269 |
+
return
|
270 |
else:
|
271 |
+
# CSS افتراضي
|
272 |
+
css_content = """
|
273 |
+
.sidebar .sidebar-content {
|
274 |
+
direction: rtl;
|
275 |
+
text-align: right;
|
276 |
+
}
|
277 |
+
div[data-testid="stForm"] {
|
278 |
+
border: 1px solid #ddd;
|
279 |
+
padding: 10px;
|
280 |
+
border-radius: 10px;
|
281 |
+
}
|
282 |
+
.module-title {
|
283 |
+
color: #1E88E5;
|
284 |
+
text-align: center;
|
285 |
+
font-size: 1.8rem;
|
286 |
+
margin-bottom: 1rem;
|
287 |
+
}
|
288 |
+
.instructions {
|
289 |
+
background-color: #f8f9fa;
|
290 |
+
border-right: 3px solid #4CAF50;
|
291 |
+
padding: 10px;
|
292 |
+
margin-bottom: 15px;
|
293 |
+
}
|
294 |
+
.results-container {
|
295 |
+
background-color: #f5f5f5;
|
296 |
+
padding: 15px;
|
297 |
+
border-radius: 5px;
|
298 |
+
margin-top: 20px;
|
299 |
+
}
|
300 |
+
.risk-high {
|
301 |
+
color: #d32f2f;
|
302 |
+
font-weight: bold;
|
303 |
+
}
|
304 |
+
.risk-medium {
|
305 |
+
color: #f57c00;
|
306 |
+
font-weight: bold;
|
307 |
+
}
|
308 |
+
.risk-low {
|
309 |
+
color: #388e3c;
|
310 |
+
font-weight: bold;
|
311 |
+
}
|
312 |
+
.form-container {
|
313 |
+
background-color: #f9f9f9;
|
314 |
+
padding: 20px;
|
315 |
+
border-radius: 10px;
|
316 |
+
margin-bottom: 20px;
|
317 |
+
}
|
318 |
+
.section-header {
|
319 |
+
color: #2196F3;
|
320 |
+
font-size: 1.2rem;
|
321 |
+
font-weight: bold;
|
322 |
+
margin-top: 20px;
|
323 |
+
margin-bottom: 10px;
|
324 |
+
border-bottom: 1px solid #eee;
|
325 |
+
padding-bottom: 5px;
|
326 |
+
}
|
327 |
+
"""
|
328 |
+
|
329 |
+
st.markdown(f"<style>{css_content}</style>", unsafe_allow_html=True)
|
330 |
+
|
331 |
+
except Exception as e:
|
332 |
+
logger.error(f"خطأ في تحميل ملف CSS: {e}")
|
utils/helpers/__init__.py
CHANGED
@@ -6,8 +6,6 @@
|
|
6 |
import streamlit as st
|
7 |
import pandas as pd
|
8 |
import numpy as np
|
9 |
-
import os
|
10 |
-
import sqlite3
|
11 |
|
12 |
from .utils import (
|
13 |
create_directory_if_not_exists,
|
@@ -25,33 +23,6 @@ from .utils import (
|
|
25 |
extract_numbers_from_text
|
26 |
)
|
27 |
|
28 |
-
def get_connection():
|
29 |
-
"""
|
30 |
-
إنشاء اتصال وهمي لقاعدة البيانات للاستخدام عند عدم توفر اتصال PostgreSQL
|
31 |
-
|
32 |
-
الإرجاع:
|
33 |
-
اتصال وهمي لقاعدة البيانات
|
34 |
-
"""
|
35 |
-
# إنشاء مجلد البيانات إذا لم يكن موجوداً
|
36 |
-
data_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'data')
|
37 |
-
os.makedirs(data_dir, exist_ok=True)
|
38 |
-
|
39 |
-
# إنشاء اتصال قاعدة بيانات SQLite محلية
|
40 |
-
db_path = os.path.join(data_dir, 'local_db.sqlite')
|
41 |
-
conn = sqlite3.connect(db_path)
|
42 |
-
|
43 |
-
# إعادة محاكاة سلوك اتصال PostgreSQL
|
44 |
-
conn.execute = conn.cursor().execute
|
45 |
-
|
46 |
-
# إضافة وظيفة وهمية للاقتطاع (commit) والإغلاق
|
47 |
-
original_close = conn.close
|
48 |
-
def enhanced_close():
|
49 |
-
conn.commit()
|
50 |
-
original_close()
|
51 |
-
conn.close = enhanced_close
|
52 |
-
|
53 |
-
return conn
|
54 |
-
|
55 |
__all__ = [
|
56 |
'create_directory_if_not_exists',
|
57 |
'get_data_folder',
|
@@ -65,6 +36,5 @@ __all__ = [
|
|
65 |
'styled_button',
|
66 |
'filter_dataframe',
|
67 |
'get_file_extension',
|
68 |
-
'extract_numbers_from_text'
|
69 |
-
'get_connection'
|
70 |
]
|
|
|
6 |
import streamlit as st
|
7 |
import pandas as pd
|
8 |
import numpy as np
|
|
|
|
|
9 |
|
10 |
from .utils import (
|
11 |
create_directory_if_not_exists,
|
|
|
23 |
extract_numbers_from_text
|
24 |
)
|
25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
__all__ = [
|
27 |
'create_directory_if_not_exists',
|
28 |
'get_data_folder',
|
|
|
36 |
'styled_button',
|
37 |
'filter_dataframe',
|
38 |
'get_file_extension',
|
39 |
+
'extract_numbers_from_text'
|
|
|
40 |
]
|