Upload 110 files
Browse files- app.py +825 -1104
- database/db_connector.py +23 -3
- modules/ai_assistant/ai_assistant.py +577 -10
- modules/document_comparison/document_comparator.py +29 -0
- modules/pricing/construction_calculator.py +65 -13
- modules/resources/resources_app.py +95 -56
- modules/risk_assessment/contract_risk_analyzer.py +89 -79
- static/css/rtl-fixes.css +66 -57
- utils/assets/logo.svg +84 -0
- utils/components/header.py +11 -3
- utils/components/sidebar.py +73 -8
- utils/css/enhanced.css +400 -0
- utils/css/main.css +405 -0
- utils/css/rtl.css +223 -0
- utils/helpers/__init__.py +31 -1
- utils/helpers/utils.py +37 -13
app.py
CHANGED
@@ -1,1253 +1,974 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import os
|
2 |
import sys
|
3 |
-
from datetime import datetime
|
4 |
-
import locale
|
5 |
import streamlit as st
|
6 |
-
|
7 |
-
import
|
8 |
|
9 |
-
#
|
10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
|
12 |
-
#
|
13 |
-
|
14 |
-
|
15 |
-
except:
|
16 |
try:
|
17 |
-
|
18 |
-
|
19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
|
21 |
-
#
|
22 |
-
|
23 |
-
page_title="نظام تحليل المناقصات",
|
24 |
-
page_icon="📊",
|
25 |
-
layout="wide",
|
26 |
-
initial_sidebar_state="expanded"
|
27 |
-
)
|
28 |
|
29 |
-
#
|
30 |
-
def
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
|
|
|
|
|
|
|
|
36 |
]
|
37 |
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
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 |
-
|
52 |
-
|
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 |
-
#
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
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 |
-
|
110 |
-
|
111 |
-
|
112 |
-
}
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
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 |
-
|
136 |
-
|
137 |
-
|
138 |
-
""", unsafe_allow_html=True)
|
139 |
|
140 |
-
#
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
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 |
-
|
|
|
|
|
160 |
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
|
|
|
|
|
|
170 |
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
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 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
""", unsafe_allow_html=True)
|
190 |
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
|
|
200 |
|
201 |
-
|
202 |
-
|
|
|
|
|
203 |
|
204 |
-
|
205 |
-
|
206 |
-
|
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 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
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 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
padding: 0.75rem 1rem;
|
254 |
-
font-weight: 600;
|
255 |
-
border: none;
|
256 |
}
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
text-align: right;
|
261 |
background-color: white;
|
|
|
|
|
|
|
|
|
|
|
262 |
}
|
263 |
-
|
264 |
-
|
|
|
|
|
|
|
265 |
}
|
266 |
-
|
267 |
-
|
|
|
|
|
268 |
}
|
269 |
-
</style>
|
270 |
-
""", unsafe_allow_html=True)
|
271 |
|
272 |
-
|
273 |
-
|
|
|
274 |
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
|
280 |
-
|
281 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
282 |
|
283 |
-
|
|
|
284 |
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
293 |
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
303 |
|
304 |
with col2:
|
305 |
-
st.
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
|
|
|
|
|
|
321 |
|
322 |
-
|
323 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
324 |
|
325 |
-
|
|
|
326 |
|
327 |
with col1:
|
328 |
st.markdown("""
|
329 |
-
<div class="
|
330 |
-
<div class="
|
331 |
-
<
|
332 |
-
<p>استخدم الذكاء الاصطناعي لتحليل المناقصات واستخراج المعلومات المهمة بدقة عالية.</p>
|
333 |
</div>
|
334 |
""", unsafe_allow_html=True)
|
335 |
|
336 |
with col2:
|
337 |
st.markdown("""
|
338 |
-
<div class="
|
339 |
-
<div class="
|
340 |
-
<
|
341 |
-
<p>استخرج البيانات من مستندات المناقصات بتنسيقات مختلفة (PDF، Word، Excel) بشكل آلي.</p>
|
342 |
</div>
|
343 |
""", unsafe_allow_html=True)
|
344 |
|
345 |
with col3:
|
346 |
st.markdown("""
|
347 |
-
<div class="
|
348 |
-
<div class="
|
349 |
-
<
|
350 |
-
<p>احصل على تحليلات متقدمة ورؤى قيمة حول المناقصات باستخدام تقنيات تحليل البيانات.</p>
|
351 |
</div>
|
352 |
""", unsafe_allow_html=True)
|
353 |
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
# دالة عرض صفحة المناقصات
|
362 |
-
def show_tenders():
|
363 |
-
# ترويسة الصفحة
|
364 |
-
st.markdown("<h1 class='main-title'>المناقصات</h1>", unsafe_allow_html=True)
|
365 |
|
366 |
-
#
|
367 |
-
col1, col2
|
368 |
|
369 |
with col1:
|
370 |
-
st.
|
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 |
-
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
"
|
444 |
-
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
450 |
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
459 |
|
460 |
-
|
461 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
462 |
|
463 |
-
with
|
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 |
-
|
485 |
-
|
486 |
-
|
487 |
-
|
488 |
-
|
489 |
-
"
|
490 |
-
|
491 |
-
|
492 |
-
|
493 |
-
"
|
494 |
-
|
495 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
496 |
|
497 |
-
|
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 |
-
|
508 |
-
|
509 |
-
|
510 |
-
|
511 |
-
cancelled_tenders = {
|
512 |
-
"رقم": [],
|
513 |
-
"اسم المناقصة": [],
|
514 |
-
"الجهة": [],
|
515 |
-
"التاريخ": [],
|
516 |
-
"القيمة (مليون ريال)": [],
|
517 |
-
"الحالة": []
|
518 |
-
}
|
519 |
|
520 |
-
|
521 |
-
|
522 |
-
|
523 |
-
|
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 |
-
|
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 |
-
|
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
|
642 |
-
<
|
643 |
-
|
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 |
-
<
|
735 |
-
<p
|
736 |
-
|
|
|
737 |
""", unsafe_allow_html=True)
|
738 |
|
739 |
-
|
740 |
-
def
|
741 |
-
|
742 |
-
st.markdown("<h1 class='
|
743 |
|
744 |
-
# وصف التحليلات
|
745 |
st.markdown("""
|
746 |
-
<div class="card
|
747 |
-
<
|
748 |
-
|
749 |
-
</div>
|
750 |
</div>
|
751 |
""", unsafe_allow_html=True)
|
752 |
|
753 |
-
#
|
754 |
-
st.markdown("
|
755 |
|
756 |
col1, col2, col3 = st.columns(3)
|
757 |
|
758 |
with col1:
|
759 |
-
|
760 |
-
|
761 |
-
|
762 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
763 |
|
764 |
with col2:
|
765 |
-
|
766 |
-
|
767 |
-
|
768 |
-
|
769 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
770 |
|
771 |
with col3:
|
772 |
-
|
773 |
-
|
774 |
-
|
775 |
-
|
776 |
-
|
777 |
-
|
778 |
-
|
779 |
-
|
780 |
-
|
781 |
-
|
782 |
-
|
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 |
-
<
|
842 |
-
<
|
843 |
-
<
|
844 |
-
|
845 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
846 |
""", unsafe_allow_html=True)
|
847 |
|
848 |
-
#
|
849 |
-
st.markdown("
|
850 |
|
851 |
-
|
852 |
-
tabs = st.tabs(["التقارير الدورية", "تقارير الأداء", "تقارير المقارنة", "تقارير مخصصة"])
|
853 |
|
854 |
-
with
|
855 |
-
st.markdown("
|
856 |
-
|
857 |
-
|
858 |
-
|
859 |
-
|
860 |
-
|
861 |
-
|
862 |
-
|
863 |
-
|
864 |
-
|
865 |
-
|
866 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
885 |
|
886 |
-
|
887 |
-
|
888 |
-
|
889 |
-
<
|
890 |
</div>
|
891 |
-
<div
|
892 |
-
<
|
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 |
-
|
918 |
-
|
919 |
-
|
920 |
-
|
921 |
-
|
922 |
-
|
923 |
-
|
924 |
-
|
925 |
-
|
926 |
-
|
927 |
-
# محتوى تقارير الأداء
|
928 |
-
st.markdown("""
|
929 |
-
<p>تقارير الأداء توفر معلومات عن أداء المناقصات والمشاريع.</p>
|
930 |
""", unsafe_allow_html=True)
|
931 |
-
|
932 |
-
|
933 |
st.markdown("""
|
934 |
-
<div
|
935 |
-
<
|
936 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
937 |
</div>
|
938 |
-
|
939 |
-
|
940 |
-
|
|
|
|
|
|
|
|
|
|
|
941 |
</div>
|
942 |
-
|
943 |
-
|
944 |
-
|
945 |
-
|
946 |
-
|
|
|
|
|
947 |
</div>
|
948 |
-
|
949 |
-
|
950 |
-
<
|
|
|
|
|
|
|
|
|
951 |
</div>
|
952 |
</div>
|
953 |
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
954 |
|
955 |
-
|
956 |
-
|
957 |
-
|
958 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
959 |
st.markdown("""
|
960 |
-
<
|
|
|
|
|
|
|
961 |
""", unsafe_allow_html=True)
|
962 |
|
963 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
964 |
st.markdown("""
|
965 |
-
<div class="card
|
966 |
-
<div class="card-
|
967 |
-
|
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 |
-
<
|
|
|
|
|
|
|
992 |
""", unsafe_allow_html=True)
|
993 |
|
994 |
-
|
995 |
-
|
996 |
-
|
997 |
-
|
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 |
-
|
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 |
-
|
|
|
1080 |
|
1081 |
-
|
1082 |
-
|
|
|
1083 |
|
1084 |
-
|
1085 |
-
|
1086 |
|
1087 |
-
with
|
1088 |
-
st.markdown("
|
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("
|
|
|
|
|
|
|
1105 |
|
1106 |
-
|
1107 |
-
|
1108 |
-
|
|
|
|
|
1109 |
|
1110 |
-
|
1111 |
-
|
1112 |
-
|
1113 |
-
|
1114 |
-
|
1115 |
-
else:
|
1116 |
-
st.success("تم تغيير كلمة المرور بنجاح")
|
1117 |
|
1118 |
-
|
1119 |
-
|
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 |
-
|
1142 |
-
|
|
|
1143 |
|
1144 |
-
|
1145 |
-
|
|
|
|
|
1146 |
|
1147 |
-
|
1148 |
-
"
|
1149 |
-
|
1150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1151 |
|
1152 |
-
|
1153 |
-
|
|
|
|
|
1154 |
|
1155 |
-
|
1156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1157 |
|
1158 |
-
|
1159 |
-
|
|
|
|
|
1160 |
|
1161 |
-
|
1162 |
-
"
|
1163 |
-
|
1164 |
-
|
1165 |
-
|
1166 |
-
|
1167 |
-
|
1168 |
-
|
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 |
-
|
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 |
-
|
1203 |
-
|
1204 |
-
|
1205 |
-
|
1206 |
-
|
1207 |
-
|
1208 |
-
|
1209 |
-
|
1210 |
-
|
1211 |
-
<div class="date-info">
|
1212 |
-
<div class="date-month">{month}</div>
|
1213 |
-
<div class="date-year">{year}</div>
|
1214 |
</div>
|
1215 |
-
|
1216 |
-
|
1217 |
-
|
1218 |
-
|
1219 |
-
|
1220 |
-
|
1221 |
-
|
1222 |
-
"
|
1223 |
-
|
1224 |
-
|
1225 |
-
|
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 |
-
|
|
|
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 pandas as pd
|
13 |
+
import numpy as np
|
14 |
|
15 |
+
# تهيئة حالة الجلسة لكل وحدات النظام
|
16 |
+
if 'page' not in st.session_state:
|
17 |
+
st.session_state.page = 'home'
|
18 |
+
if 'analysis_type' not in st.session_state:
|
19 |
+
st.session_state.analysis_type = None
|
20 |
+
if 'show_document_upload' not in st.session_state:
|
21 |
+
st.session_state.show_document_upload = False
|
22 |
+
if 'report_type' not in st.session_state:
|
23 |
+
st.session_state.report_type = None
|
24 |
+
if 'show_report_form' not in st.session_state:
|
25 |
+
st.session_state.show_report_form = False
|
26 |
+
if 'analysis_result' not in st.session_state:
|
27 |
+
st.session_state.analysis_result = None
|
28 |
+
if 'current_document' not in st.session_state:
|
29 |
+
st.session_state.current_document = None
|
30 |
+
if 'current_document_text' not in st.session_state:
|
31 |
+
st.session_state.current_document_text = None
|
32 |
+
if 'loaded_files' not in st.session_state:
|
33 |
+
st.session_state.loaded_files = []
|
34 |
+
if 'notifications' not in st.session_state:
|
35 |
+
st.session_state.notifications = []
|
36 |
|
37 |
+
# وظيفة لتهيئة حزم NLTK المطلوبة عند بدء التطبيق
|
38 |
+
def initialize_nltk_resources():
|
39 |
+
"""تنزيل وتهيئة موارد NLTK المطلوبة"""
|
|
|
40 |
try:
|
41 |
+
# محاولة تنزيل حزم NLTK الأساسية
|
42 |
+
import nltk
|
43 |
+
|
44 |
+
# قائمة بالحزم المطلوبة
|
45 |
+
required_packages = ['punkt', 'stopwords', 'wordnet']
|
46 |
+
for package in required_packages:
|
47 |
+
try:
|
48 |
+
# محاولة استخدام الحزمة أولاً، وإذا فشلت يتم تنزيلها
|
49 |
+
nltk.data.find(f'tokenizers/{package}')
|
50 |
+
except LookupError:
|
51 |
+
print(f"تنزيل حزمة NLTK: {package}")
|
52 |
+
nltk.download(package, quiet=True)
|
53 |
+
|
54 |
+
print("تم تهيئة موارد NLTK بنجاح.")
|
55 |
+
except Exception as e:
|
56 |
+
print(f"خطأ في تهيئة NLTK: {e}")
|
57 |
|
58 |
+
# تهيئة موارد NLTK عند بدء التطبيق
|
59 |
+
initialize_nltk_resources()
|
|
|
|
|
|
|
|
|
|
|
60 |
|
61 |
+
# مسار نسبي للملفات الثابتة (للتأكد من العمل في بيئات مختلفة)
|
62 |
+
def get_static_path(file_path):
|
63 |
+
"""مسار ملف ثابت يعمل سواء كان التشغيل من المجلد الرئيسي أو من المجلد الفرعي"""
|
64 |
+
# قائمة المسارات المحتملة
|
65 |
+
possible_paths = [
|
66 |
+
# المسار المباشر (في حالة تشغيل التطبيق من نفس المجلد)
|
67 |
+
file_path,
|
68 |
+
# المسار النسبي من مجلد التطبيق (tender-analysis-system)
|
69 |
+
os.path.join(os.path.dirname(__file__), file_path),
|
70 |
+
# المسار النسبي من المجلد الأعلى
|
71 |
+
os.path.join(os.path.dirname(os.path.dirname(__file__)), "tender-analysis-system", file_path),
|
72 |
]
|
73 |
|
74 |
+
# اختبار كل مسار محتمل
|
75 |
+
for path in possible_paths:
|
76 |
+
if os.path.exists(path):
|
77 |
+
return path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
78 |
|
79 |
+
# إذا لم يتم العثور على الملف، إعادة المسار الأصلي
|
80 |
+
return file_path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
|
82 |
+
# إعداد إعدادات الصفحة
|
83 |
+
try:
|
84 |
+
st.set_page_config(
|
85 |
+
page_title="نظام WAHBi للذكاء الاصطناعي | التعاقدات والمناقصات",
|
86 |
+
page_icon="📊",
|
87 |
+
layout="wide",
|
88 |
+
initial_sidebar_state="expanded"
|
89 |
+
)
|
90 |
+
except Exception as e:
|
91 |
+
print(f"خطأ في إعداد الصفحة: {e}")
|
92 |
+
# يحدث هذا غالبًا عند استخدام st.set_page_config أكثر من مرة
|
93 |
|
94 |
+
# استيراد ملفات CSS الجديدة
|
95 |
+
try:
|
96 |
+
# ملف CSS الرئيسي
|
97 |
+
main_css_path = get_static_path("utils/css/main.css")
|
98 |
+
if os.path.exists(main_css_path):
|
99 |
+
with open(main_css_path, "r", encoding='utf-8') as f:
|
100 |
+
main_css = f.read()
|
101 |
+
st.markdown(f"<style>{main_css}</style>", unsafe_allow_html=True)
|
102 |
+
else:
|
103 |
+
print(f"تعذر العثور على ملف CSS الرئيسي: {main_css_path}")
|
104 |
+
|
105 |
+
# ملف CSS للإصلاحات RTL
|
106 |
+
rtl_css_path = get_static_path("utils/css/rtl.css")
|
107 |
+
if os.path.exists(rtl_css_path):
|
108 |
+
with open(rtl_css_path, "r", encoding='utf-8') as f:
|
109 |
+
rtl_css = f.read()
|
110 |
+
st.markdown(f"<style>{rtl_css}</style>", unsafe_allow_html=True)
|
111 |
+
else:
|
112 |
+
print(f"تعذر العثور على ملف CSS للتوجيه RTL: {rtl_css_path}")
|
113 |
+
|
114 |
+
# ملف CSS للتحسينات المتقدمة
|
115 |
+
enhanced_css_path = get_static_path("utils/css/enhanced.css")
|
116 |
+
if os.path.exists(enhanced_css_path):
|
117 |
+
with open(enhanced_css_path, "r", encoding='utf-8') as f:
|
118 |
+
enhanced_css = f.read()
|
119 |
+
st.markdown(f"<style>{enhanced_css}</style>", unsafe_allow_html=True)
|
120 |
+
else:
|
121 |
+
print(f"تعذر العثور على ملف CSS المحسن: {enhanced_css_path}")
|
122 |
+
except Exception as e:
|
123 |
+
st.warning(f"حدث خطأ أثناء تحميل ملفات CSS: {str(e)}")
|
124 |
+
print(f"خطأ في تحميل ملفات CSS: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
125 |
|
126 |
+
# إضافة Font Awesome وأي أصول خارجية أخرى
|
127 |
+
st.markdown("""
|
128 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
129 |
+
""", unsafe_allow_html=True)
|
|
|
130 |
|
131 |
+
# إضافة CSS المخصص
|
132 |
+
st.markdown("""
|
133 |
+
<style>
|
134 |
+
/* تعديل الاتجاه للدعم العربي */
|
135 |
+
.css-18e3th9, .css-1d391kg, .stMarkdown, .stTextArea, .stButton, .stTextInput, .stSelectbox, .stRadio {
|
136 |
+
direction: rtl;
|
137 |
+
text-align: right;
|
|
|
|
|
|
|
138 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
139 |
|
140 |
+
/* تحسين مظهر العناوين */
|
141 |
+
h1, h2, h3, h4 {
|
142 |
+
color: #1E88E5;
|
143 |
+
}
|
144 |
|
145 |
+
/* تخصيص عنوان التطبيق */
|
146 |
+
.app-title {
|
147 |
+
font-size: 2.2rem;
|
148 |
+
font-weight: bold;
|
149 |
+
text-align: center;
|
150 |
+
color: #1E88E5;
|
151 |
+
margin-bottom: 1rem;
|
152 |
+
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
|
153 |
+
background: linear-gradient(90deg, #1976D2, #64B5F6);
|
154 |
+
-webkit-background-clip: text;
|
155 |
+
-webkit-text-fill-color: transparent;
|
156 |
+
}
|
157 |
|
158 |
+
/* تخصيص الشريط الجانبي */
|
159 |
+
.sidebar .sidebar-content {
|
160 |
+
background-color: #f8f9fa;
|
161 |
+
}
|
|
|
|
|
|
|
|
|
|
|
162 |
|
163 |
+
/* تخصيص الأقسام */
|
164 |
+
.section-card {
|
165 |
+
background-color: #f9f9f9;
|
166 |
+
border-radius: 10px;
|
167 |
+
padding: 20px;
|
168 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
169 |
+
margin-bottom: 20px;
|
170 |
+
}
|
|
|
171 |
|
172 |
+
/* تخصيص الأزرار */
|
173 |
+
.stButton>button {
|
174 |
+
border-radius: 5px;
|
175 |
+
background-color: #1E88E5;
|
176 |
+
color: white;
|
177 |
+
font-weight: bold;
|
178 |
+
border: none;
|
179 |
+
padding: 0.5rem 1rem;
|
180 |
+
transition: all 0.3s ease;
|
181 |
+
}
|
182 |
|
183 |
+
.stButton>button:hover {
|
184 |
+
background-color: #1565C0;
|
185 |
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
186 |
+
}
|
187 |
|
188 |
+
/* تخصيص المؤشرات */
|
189 |
+
.indicator {
|
190 |
+
padding: 1rem;
|
191 |
+
border-radius: 10px;
|
192 |
+
background-color: #f5f5f5;
|
193 |
+
text-align: center;
|
194 |
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
195 |
}
|
196 |
|
197 |
+
.indicator-value {
|
198 |
+
font-size: 2rem;
|
199 |
+
font-weight: bold;
|
200 |
+
margin-bottom: 0.5rem;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
201 |
}
|
202 |
+
|
203 |
+
.indicator-label {
|
204 |
+
font-size: 1rem;
|
205 |
+
color: #666;
|
|
|
|
|
|
|
206 |
}
|
207 |
+
|
208 |
+
/* تخصيص البطاقات */
|
209 |
+
.card {
|
|
|
210 |
background-color: white;
|
211 |
+
border-radius: 10px;
|
212 |
+
padding: 15px;
|
213 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
214 |
+
margin-bottom: 15px;
|
215 |
+
border-right: 4px solid #1E88E5;
|
216 |
}
|
217 |
+
|
218 |
+
.card-title {
|
219 |
+
font-weight: bold;
|
220 |
+
color: #1E88E5;
|
221 |
+
margin-bottom: 10px;
|
222 |
}
|
223 |
+
|
224 |
+
.card-metrics {
|
225 |
+
display: flex;
|
226 |
+
justify-content: space-between;
|
227 |
}
|
|
|
|
|
228 |
|
229 |
+
.card-metric {
|
230 |
+
text-align: center;
|
231 |
+
}
|
232 |
|
233 |
+
.card-metric-value {
|
234 |
+
font-weight: bold;
|
235 |
+
font-size: 1.5rem;
|
236 |
+
}
|
237 |
|
238 |
+
.card-metric-label {
|
239 |
+
font-size: 0.8rem;
|
240 |
+
color: #666;
|
241 |
+
}
|
242 |
+
</style>
|
243 |
+
""", unsafe_allow_html=True)
|
244 |
+
|
245 |
+
# استيراد المكونات والوحدات
|
246 |
+
from utils.components.sidebar import render_sidebar
|
247 |
+
from utils.helpers import create_directory_if_not_exists, get_data_folder
|
248 |
+
|
249 |
+
# استيراد وحدات التطبيق
|
250 |
+
from modules.pricing.pricing_app import PricingApp
|
251 |
+
from modules.projects.projects_app import ProjectsApp
|
252 |
+
from modules.resources.resources_app import ResourcesApp
|
253 |
+
from modules.risk_assessment.risk_assessment_app import RiskAssessmentApp
|
254 |
+
from modules.project_tracker.tracker_app import TrackerApp
|
255 |
+
from modules.maps.maps_app import MapsApp
|
256 |
+
from modules.notifications.notifications_app import NotificationsApp
|
257 |
+
from modules.voice_narration.voice_narration_app import VoiceNarrationApp
|
258 |
+
from modules.achievements.achievements_app import AchievementsApp
|
259 |
+
from modules.ai_finetuning.finetuning_app import FinetuningApp
|
260 |
+
from modules.document_comparison.comparison_app import DocumentComparisonApp
|
261 |
+
|
262 |
+
# إنشاء مجلدات البيانات الضرورية
|
263 |
+
create_directory_if_not_exists(get_data_folder())
|
264 |
+
create_directory_if_not_exists(os.path.join(get_data_folder(), "projects"))
|
265 |
+
create_directory_if_not_exists(os.path.join(get_data_folder(), "documents"))
|
266 |
+
create_directory_if_not_exists(os.path.join(get_data_folder(), "analysis"))
|
267 |
+
|
268 |
+
def main():
|
269 |
+
"""الدالة الرئيسية للتطبيق"""
|
270 |
|
271 |
+
# تقديم الشريط الجانبي وتلقي الوحدة المختارة
|
272 |
+
selected_module = render_sidebar()
|
273 |
|
274 |
+
# إذا كان المستخدم غير مصرح له، قم بإظهار شاشة تسجيل الدخول
|
275 |
+
if "is_authenticated" in st.session_state and not st.session_state.is_authenticated:
|
276 |
+
render_login_screen()
|
277 |
+
return
|
278 |
+
|
279 |
+
# إظهار الوحدة المختارة
|
280 |
+
if selected_module == "الرئيسية":
|
281 |
+
render_homepage()
|
282 |
+
|
283 |
+
elif selected_module == "إدارة المشاريع":
|
284 |
+
projects_app = ProjectsApp()
|
285 |
+
projects_app.render()
|
286 |
+
|
287 |
+
elif selected_module == "التسعير المتكاملة":
|
288 |
+
pricing_app = PricingApp()
|
289 |
+
pricing_app.render()
|
290 |
+
|
291 |
+
elif selected_module == "الموارد والتكاليف":
|
292 |
+
resources_app = ResourcesApp()
|
293 |
+
resources_app.render()
|
294 |
+
|
295 |
+
elif selected_module == "تحليل المستندات":
|
296 |
+
# تقديم واجهة تحليل المستندات
|
297 |
+
render_document_analysis()
|
298 |
+
|
299 |
+
elif selected_module == "مقارنة المستندات":
|
300 |
+
# تقديم واجهة مقارنة المستندات
|
301 |
+
comparison_app = DocumentComparisonApp()
|
302 |
+
comparison_app.render()
|
303 |
+
|
304 |
+
elif selected_module == "تقييم مخاطر العقود":
|
305 |
+
risk_app = RiskAssessmentApp()
|
306 |
+
risk_app.render()
|
307 |
+
|
308 |
+
elif selected_module == "التقارير والتحليلات":
|
309 |
+
# تقديم واجهة التقارير والتحليلات
|
310 |
+
render_reports_and_analytics()
|
311 |
+
|
312 |
+
elif selected_module == "متتبع حالة المشروع":
|
313 |
+
tracker_app = TrackerApp()
|
314 |
+
tracker_app.render()
|
315 |
+
|
316 |
+
elif selected_module == "خريطة المشاريع":
|
317 |
+
maps_app = MapsApp()
|
318 |
+
maps_app.render()
|
319 |
+
|
320 |
+
elif selected_module == "نظام الإشعارات":
|
321 |
+
notifications_app = NotificationsApp()
|
322 |
+
notifications_app.render()
|
323 |
+
|
324 |
+
elif selected_module == "الترجمة الصوتية":
|
325 |
+
voice_app = VoiceNarrationApp()
|
326 |
+
voice_app.render()
|
327 |
+
|
328 |
+
elif selected_module == "نظام الإنجازات":
|
329 |
+
achievements_app = AchievementsApp()
|
330 |
+
achievements_app.render()
|
331 |
+
|
332 |
+
elif selected_module == "المساعد الذكي":
|
333 |
+
# تقديم واجهة المساعد الذكي
|
334 |
+
render_ai_assistant()
|
335 |
+
|
336 |
+
elif selected_module == "ضبط نماذج الذكاء الاصطناعي":
|
337 |
+
finetuning_app = FinetuningApp()
|
338 |
+
finetuning_app.render()
|
339 |
|
340 |
+
else:
|
341 |
+
st.error("الوحدة المطلوبة غير موجودة")
|
342 |
+
|
343 |
+
|
344 |
+
def render_login_screen():
|
345 |
+
"""عرض شاشة تسجيل الدخول"""
|
346 |
+
st.markdown("<h1 class='app-title'>نظام WAHBi للذكاء الاصطناعي</h1>", unsafe_allow_html=True)
|
347 |
+
|
348 |
+
st.markdown("""
|
349 |
+
<div class="section-card">
|
350 |
+
<h2>تسجيل الدخول</h2>
|
351 |
+
<p>يرجى إدخال بيانات الاعتماد الخاصة بك للوصول إلى النظام.</p>
|
352 |
+
</div>
|
353 |
+
""", unsafe_allow_html=True)
|
354 |
+
|
355 |
+
col1, col2, col3 = st.columns([1, 2, 1])
|
356 |
|
357 |
with col2:
|
358 |
+
username = st.text_input("اسم المستخدم")
|
359 |
+
password = st.text_input("كلمة المرور", type="password")
|
360 |
+
|
361 |
+
if st.button("تسجيل الدخول"):
|
362 |
+
# تنفيذ منطق المصادقة
|
363 |
+
if username == "admin" and password == "admin": # بيانات اعتماد مؤقتة للتطوير
|
364 |
+
st.session_state.is_authenticated = True
|
365 |
+
st.session_state.user_info = {
|
366 |
+
"id": 1,
|
367 |
+
"username": "admin",
|
368 |
+
"full_name": "مدير النظام",
|
369 |
+
"email": "[email protected]",
|
370 |
+
"role": "مدير",
|
371 |
+
"department": "الإدارة",
|
372 |
+
"last_login": "2023-01-01 09:00:00"
|
373 |
+
}
|
374 |
+
st.rerun()
|
375 |
+
else:
|
376 |
+
st.error("اسم المستخدم أو كلمة المرور غير صحيحة")
|
377 |
|
378 |
+
st.markdown("""
|
379 |
+
<div style="text-align: center; margin-top: 50px; color: #666;">
|
380 |
+
<p>نظام WAHBi للذكاء الاصطناعي © 2025 شركة شبه الجزيرة للمقاولات</p>
|
381 |
+
<p>جميع الحقوق محفوظة</p>
|
382 |
+
</div>
|
383 |
+
""", unsafe_allow_html=True)
|
384 |
+
|
385 |
+
|
386 |
+
def render_homepage():
|
387 |
+
"""عرض الصفحة الرئيسية للتطبيق"""
|
388 |
+
st.markdown("<h1 class='app-title'>نظام WAHBi للذكاء الاصطناعي</h1>", unsafe_allow_html=True)
|
389 |
+
st.markdown("<div style='text-align: center; margin-bottom: 20px;'>نظام متكامل لتحليل العقود والمناقصات باستخدام تقنيات الذكاء الاصطناعي المتقدمة</div>", unsafe_allow_html=True)
|
390 |
|
391 |
+
# عرض مؤشرات الأداء الرئيسية
|
392 |
+
col1, col2, col3, col4 = st.columns(4)
|
393 |
|
394 |
with col1:
|
395 |
st.markdown("""
|
396 |
+
<div class="indicator">
|
397 |
+
<div class="indicator-value" style="color: #1E88E5;">24</div>
|
398 |
+
<div class="indicator-label">المناقصات النشطة</div>
|
|
|
399 |
</div>
|
400 |
""", unsafe_allow_html=True)
|
401 |
|
402 |
with col2:
|
403 |
st.markdown("""
|
404 |
+
<div class="indicator">
|
405 |
+
<div class="indicator-value" style="color: #43A047;">8</div>
|
406 |
+
<div class="indicator-label">مشاريع قيد التنفيذ</div>
|
|
|
407 |
</div>
|
408 |
""", unsafe_allow_html=True)
|
409 |
|
410 |
with col3:
|
411 |
st.markdown("""
|
412 |
+
<div class="indicator">
|
413 |
+
<div class="indicator-value" style="color: #FB8C00;">12</div>
|
414 |
+
<div class="indicator-label">مستندات قيد التحليل</div>
|
|
|
415 |
</div>
|
416 |
""", unsafe_allow_html=True)
|
417 |
|
418 |
+
with col4:
|
419 |
+
st.markdown("""
|
420 |
+
<div class="indicator">
|
421 |
+
<div class="indicator-value" style="color: #E53935;">5</div>
|
422 |
+
<div class="indicator-label">تنبيهات تتطلب الاهتمام</div>
|
423 |
+
</div>
|
424 |
+
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
425 |
|
426 |
+
# عرض المشاريع الأخيرة والوصول السريع
|
427 |
+
col1, col2 = st.columns([2, 1])
|
428 |
|
429 |
with col1:
|
430 |
+
st.markdown("### المناقصات الأخيرة")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
431 |
|
432 |
+
st.markdown("""
|
433 |
+
<div class="card">
|
434 |
+
<div class="card-title">إنشاء طريق سريع بمنطقة الرياض</div>
|
435 |
+
<div>رقم المناقصة: TR-2025-134</div>
|
436 |
+
<div>الجهة المالكة: وزارة النقل</div>
|
437 |
+
<div>تاريخ الإغلاق: 15 أبريل 2025</div>
|
438 |
+
<div class="card-metrics" style="margin-top: 10px;">
|
439 |
+
<div class="card-metric">
|
440 |
+
<div class="card-metric-value" style="color: #4CAF50;">85%</div>
|
441 |
+
<div class="card-metric-label">نسبة الإنجاز</div>
|
442 |
+
</div>
|
443 |
+
<div class="card-metric">
|
444 |
+
<div class="card-metric-value" style="color: #FFC107;">متوسطة</div>
|
445 |
+
<div class="card-metric-label">المخاطر</div>
|
446 |
+
</div>
|
447 |
+
<div class="card-metric">
|
448 |
+
<div class="card-metric-value" style="color: #2196F3;">مرتفعة</div>
|
449 |
+
<div class="card-metric-label">الأولوية</div>
|
450 |
+
</div>
|
451 |
+
</div>
|
452 |
+
</div>
|
453 |
|
454 |
+
<div class="card">
|
455 |
+
<div class="card-title">تطوير شبكة الصرف الصحي بالمنطقة الشرقية</div>
|
456 |
+
<div>رقم المناقصة: WS-2025-089</div>
|
457 |
+
<div>الجهة المالكة: وزارة المياه</div>
|
458 |
+
<div>تاريخ الإغلاق: 22 أبريل 2025</div>
|
459 |
+
<div class="card-metrics" style="margin-top: 10px;">
|
460 |
+
<div class="card-metric">
|
461 |
+
<div class="card-metric-value" style="color: #4CAF50;">62%</div>
|
462 |
+
<div class="card-metric-label">نسبة الإنجاز</div>
|
463 |
+
</div>
|
464 |
+
<div class="card-metric">
|
465 |
+
<div class="card-metric-value" style="color: #F44336;">مرتفعة</div>
|
466 |
+
<div class="card-metric-label">المخاطر</div>
|
467 |
+
</div>
|
468 |
+
<div class="card-metric">
|
469 |
+
<div class="card-metric-value" style="color: #2196F3;">مرتفعة</div>
|
470 |
+
<div class="card-metric-label">الأولوية</div>
|
471 |
+
</div>
|
472 |
+
</div>
|
473 |
+
</div>
|
474 |
|
475 |
+
<div class="card">
|
476 |
+
<div class="card-title">بناء 3 مدارس بمنطقة مكة المكرمة</div>
|
477 |
+
<div>رقم المناقصة: ED-2025-112</div>
|
478 |
+
<div>الجهة المالكة: وزارة التعليم</div>
|
479 |
+
<div>تاريخ الإغلاق: 5 مايو 2025</div>
|
480 |
+
<div class="card-metrics" style="margin-top: 10px;">
|
481 |
+
<div class="card-metric">
|
482 |
+
<div class="card-metric-value" style="color: #4CAF50;">38%</div>
|
483 |
+
<div class="card-metric-label">نسبة الإنجاز</div>
|
484 |
+
</div>
|
485 |
+
<div class="card-metric">
|
486 |
+
<div class="card-metric-value" style="color: #4CAF50;">منخفضة</div>
|
487 |
+
<div class="card-metric-label">المخاطر</div>
|
488 |
+
</div>
|
489 |
+
<div class="card-metric">
|
490 |
+
<div class="card-metric-value" style="color: #FFC107;">متوسطة</div>
|
491 |
+
<div class="card-metric-label">الأولوية</div>
|
492 |
+
</div>
|
493 |
+
</div>
|
494 |
+
</div>
|
495 |
+
""", unsafe_allow_html=True)
|
496 |
|
497 |
+
with col2:
|
498 |
+
st.markdown("### الوصول السريع")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
499 |
|
500 |
+
st.markdown("""
|
501 |
+
<div style="display: grid; gap: 10px;">
|
502 |
+
<button style="background-color: #1E88E5; color: white; border: none; border-radius: 5px; padding: 10px; text-align: right; cursor: pointer; font-weight: bold;">
|
503 |
+
<i class="fas fa-file-alt" style="margin-left: 10px;"></i> تحليل مستند جديد
|
504 |
+
</button>
|
505 |
+
|
506 |
+
<button style="background-color: #43A047; color: white; border: none; border-radius: 5px; padding: 10px; text-align: right; cursor: pointer; font-weight: bold;">
|
507 |
+
<i class="fas fa-calculator" style="margin-left: 10px;"></i> حساب تكاليف مشروع
|
508 |
+
</button>
|
509 |
+
|
510 |
+
<button style="background-color: #FB8C00; color: white; border: none; border-radius: 5px; padding: 10px; text-align: right; cursor: pointer; font-weight: bold;">
|
511 |
+
<i class="fas fa-exclamation-triangle" style="margin-left: 10px;"></i> تقييم مخاطر العقد
|
512 |
+
</button>
|
513 |
+
|
514 |
+
<button style="background-color: #8E24AA; color: white; border: none; border-radius: 5px; padding: 10px; text-align: right; cursor: pointer; font-weight: bold;">
|
515 |
+
<i class="fas fa-map-marker-alt" style="margin-left: 10px;"></i> استعراض خريطة المشاريع
|
516 |
+
</button>
|
517 |
+
|
518 |
+
<button style="background-color: #546E7A; color: white; border: none; border-radius: 5px; padding: 10px; text-align: right; cursor: pointer; font-weight: bold;">
|
519 |
+
<i class="fas fa-chart-bar" style="margin-left: 10px;"></i> إنشاء تقارير تحليلية
|
520 |
+
</button>
|
521 |
+
</div>
|
522 |
+
""", unsafe_allow_html=True)
|
523 |
|
524 |
+
st.markdown("### آخر التنبيهات")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
525 |
|
526 |
+
st.markdown("""
|
527 |
+
<div style="background-color: #FFEBEE; border-right: 3px solid #E53935; padding: 10px; margin-bottom: 10px; border-radius: 5px;">
|
528 |
+
<div style="font-weight: bold; color: #B71C1C;">انتهاء موعد تقديم المناقصة</div>
|
529 |
+
<div style="font-size: 0.9rem;">مشروع إنشاء الطريق السريع - متبقي 3 أيام</div>
|
530 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
531 |
|
532 |
+
<div style="background-color: #FFF8E1; border-right: 3px solid #FFA000; padding: 10px; margin-bottom: 10px; border-radius: 5px;">
|
533 |
+
<div style="font-weight: bold; color: #FF6F00;">تغيير في شروط المناقصة</div>
|
534 |
+
<div style="font-size: 0.9rem;">تم تحديث مستندات مشروع شبكة الصرف الصحي</div>
|
535 |
+
</div>
|
|
|
|
|
|
|
|
|
536 |
|
537 |
+
<div style="background-color: #E8F5E9; border-right: 3px solid #43A047; padding: 10px; margin-bottom: 10px; border-radius: 5px;">
|
538 |
+
<div style="font-weight: bold; color: #2E7D32;">إكمال تحليل المستند</div>
|
539 |
+
<div style="font-size: 0.9rem;">اكتمل تحليل عقد بناء المدارس بنجاح</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
540 |
</div>
|
541 |
+
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
542 |
|
543 |
+
# معلومات حول النظام
|
544 |
+
st.markdown("---")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
545 |
|
|
|
546 |
st.markdown("""
|
547 |
+
<div class="section-card">
|
548 |
+
<h3>حول النظام</h3>
|
549 |
+
<p>نظام WAHBi للذكاء الاصطناعي هو نظام متكامل لتحليل العقود والمناقصات وإدارة المشاريع، مصمم خصيصاً لشركات المقاولات والبناء. يستخدم النظام تقنيات الذكاء الاصطناعي المتقدمة لتحليل المستندات واستخراج المعلومات المهمة وتقييم المخاطر ودعم اتخاذ القرار.</p>
|
|
|
550 |
</div>
|
551 |
""", unsafe_allow_html=True)
|
552 |
|
553 |
+
# معلومات الشركة
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
554 |
st.markdown("""
|
555 |
+
<div style="text-align: center; margin-top: 30px; color: #666;">
|
556 |
+
<p>هذا النظام يعمل لشركة شبه الجزيرة للمقاولات</p>
|
557 |
+
<p>جميع الحقوق محفوظة 2025</p>
|
558 |
+
</div>
|
559 |
""", unsafe_allow_html=True)
|
560 |
|
561 |
+
|
562 |
+
def render_document_analysis():
|
563 |
+
"""عرض واجهة تحليل المستندات"""
|
564 |
+
st.markdown("<h1 class='app-title'>تحليل المستندات</h1>", unsafe_allow_html=True)
|
565 |
|
|
|
566 |
st.markdown("""
|
567 |
+
<div class="section-card">
|
568 |
+
<p>استخدم هذه الوحدة لتحليل مستندات العقود والمناقصات باستخدام تقنيات الذكاء الاصطناعي المتقدمة.
|
569 |
+
يمكنك تحميل المستندات بتنسيقات PDF أو Word وسيقوم النظام بتحليلها واستخراج المعلومات المهمة مثل الشروط والتكاليف والمخاطر والتزاماتك كمقاول.</p>
|
|
|
570 |
</div>
|
571 |
""", unsafe_allow_html=True)
|
572 |
|
573 |
+
# أدوات التحليل
|
574 |
+
st.markdown("### أدوات التحليل:", unsafe_allow_html=True)
|
575 |
|
576 |
col1, col2, col3 = st.columns(3)
|
577 |
|
578 |
with col1:
|
579 |
+
st.markdown("""
|
580 |
+
<div class="card">
|
581 |
+
<div class="card-title">تحليل العقد الشامل</div>
|
582 |
+
<p>تحليل شامل للعقد باستخدام Claude AI لاستخراج جميع البنود والشروط والالتزامات والمواعيد النهائية.</p>
|
583 |
+
</div>
|
584 |
+
""", unsafe_allow_html=True)
|
585 |
+
|
586 |
+
if st.button("تحليل جديد", key="btn_complete_analysis"):
|
587 |
+
# هنا سيتم استدعاء وحدة تحليل العقد الشامل
|
588 |
+
st.session_state.analysis_type = "complete"
|
589 |
+
st.session_state.show_document_upload = True
|
590 |
+
st.experimental_rerun()
|
591 |
|
592 |
with col2:
|
593 |
+
st.markdown("""
|
594 |
+
<div class="card">
|
595 |
+
<div class="card-title">تحليل جداول الكميات</div>
|
596 |
+
<p>تحليل متخصص لجداول الكميات (BOQ) لاستخراج قوائم المواد والكميات والأسعار والتكاليف الإجمالية.</p>
|
597 |
+
</div>
|
598 |
+
""", unsafe_allow_html=True)
|
599 |
+
|
600 |
+
if st.button("تحليل جديد", key="btn_boq_analysis"):
|
601 |
+
# هنا سيتم استدعاء وحدة تحل��ل جداول الكميات
|
602 |
+
st.session_state.analysis_type = "boq"
|
603 |
+
st.session_state.show_document_upload = True
|
604 |
+
st.experimental_rerun()
|
605 |
|
606 |
with col3:
|
607 |
+
st.markdown("""
|
608 |
+
<div class="card">
|
609 |
+
<div class="card-title">تحليل الشروط والأحكام</div>
|
610 |
+
<p>تحليل متخصص للشروط والأحكام في العقد لتحديد الشروط الغير عادية أو المقيدة والمخاطر القانونية.</p>
|
611 |
+
</div>
|
612 |
+
""", unsafe_allow_html=True)
|
613 |
+
|
614 |
+
if st.button("تحليل جديد", key="btn_terms_analysis"):
|
615 |
+
# هنا سيتم استدعاء وحدة تحليل الشروط والأحكام
|
616 |
+
st.session_state.analysis_type = "terms"
|
617 |
+
st.session_state.show_document_upload = True
|
618 |
+
st.experimental_rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
619 |
|
620 |
+
# التحليلات الأخيرة
|
621 |
+
st.markdown("### التحليلات الأخيرة")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
622 |
|
|
|
623 |
st.markdown("""
|
624 |
+
<table style="width: 100%; border-collapse: collapse; margin-top: 20px;">
|
625 |
+
<thead>
|
626 |
+
<tr style="background-color: #f5f5f5;">
|
627 |
+
<th style="padding: 12px; text-align: right; border-bottom: 2px solid #ddd;">اسم المستند</th>
|
628 |
+
<th style="padding: 12px; text-align: right; border-bottom: 2px solid #ddd;">نوع التحليل</th>
|
629 |
+
<th style="padding: 12px; text-align: right; border-bottom: 2px solid #ddd;">تاريخ التحليل</th>
|
630 |
+
<th style="padding: 12px; text-align: right; border-bottom: 2px solid #ddd;">الحالة</th>
|
631 |
+
<th style="padding: 12px; text-align: right; border-bottom: 2px solid #ddd;">الإجراءات</th>
|
632 |
+
</tr>
|
633 |
+
</thead>
|
634 |
+
<tbody>
|
635 |
+
<tr>
|
636 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">عقد إنشاء طريق سريع.pdf</td>
|
637 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">تحليل شامل</td>
|
638 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">2023-03-25</td>
|
639 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;"><span style="color: #4CAF50; font-weight: bold;">مكتم��</span></td>
|
640 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">
|
641 |
+
<button style="background-color: #1E88E5; color: white; border: none; border-radius: 3px; padding: 5px 10px; margin-left: 5px; cursor: pointer;">عرض</button>
|
642 |
+
<button style="background-color: #78909C; color: white; border: none; border-radius: 3px; padding: 5px 10px; cursor: pointer;">تنزيل التقرير</button>
|
643 |
+
</td>
|
644 |
+
</tr>
|
645 |
+
<tr style="background-color: #f9f9f9;">
|
646 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">جداول كميات مشروع صرف صحي.xlsx</td>
|
647 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">تحليل جداول الكميات</td>
|
648 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">2023-03-23</td>
|
649 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;"><span style="color: #4CAF50; font-weight: bold;">مكتمل</span></td>
|
650 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">
|
651 |
+
<button style="background-color: #1E88E5; color: white; border: none; border-radius: 3px; padding: 5px 10px; margin-left: 5px; cursor: pointer;">عرض</button>
|
652 |
+
<button style="background-color: #78909C; color: white; border: none; border-radius: 3px; padding: 5px 10px; cursor: pointer;">تنزيل التقرير</button>
|
653 |
+
</td>
|
654 |
+
</tr>
|
655 |
+
<tr>
|
656 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">شروط وأحكام عقد بناء مدارس.pdf</td>
|
657 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">تحليل الشروط والأحكام</td>
|
658 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">2023-03-20</td>
|
659 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;"><span style="color: #4CAF50; font-weight: bold;">مكتمل</span></td>
|
660 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">
|
661 |
+
<button style="background-color: #1E88E5; color: white; border: none; border-radius: 3px; padding: 5px 10px; margin-left: 5px; cursor: pointer;">عرض</button>
|
662 |
+
<button style="background-color: #78909C; color: white; border: none; border-radius: 3px; padding: 5px 10px; cursor: pointer;">تنزيل التقرير</button>
|
663 |
+
</td>
|
664 |
+
</tr>
|
665 |
+
<tr style="background-color: #f9f9f9;">
|
666 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">ملحق عقد مشروع كباري.pdf</td>
|
667 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">تحليل شامل</td>
|
668 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">2023-03-18</td>
|
669 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;"><span style="color: #FB8C00; font-weight: bold;">قيد المعالجة</span></td>
|
670 |
+
<td style="padding: 12px; text-align: right; border-bottom: 1px solid #ddd;">
|
671 |
+
<button style="background-color: #9E9E9E; color: white; border: none; border-radius: 3px; padding: 5px 10px; margin-left: 5px; cursor: pointer;" disabled>عرض</button>
|
672 |
+
<button style="background-color: #9E9E9E; color: white; border: none; border-radius: 3px; padding: 5px 10px; cursor: pointer;" disabled>تنزيل التقرير</button>
|
673 |
+
</td>
|
674 |
+
</tr>
|
675 |
+
</tbody>
|
676 |
+
</table>
|
677 |
""", unsafe_allow_html=True)
|
678 |
|
679 |
+
# إحصائيات التحليل
|
680 |
+
st.markdown("### إحصائيات التحليل")
|
681 |
|
682 |
+
col1, col2 = st.columns(2)
|
|
|
683 |
|
684 |
+
with col1:
|
685 |
+
st.markdown("""
|
686 |
+
<div style="padding: 20px; background-color: #f5f5f5; border-radius: 10px; height: 100%;">
|
687 |
+
<h4 style="color: #1E88E5; margin-bottom: 15px;">توزيع أنواع المستندات</h4>
|
688 |
+
<div style="margin-bottom: 15px;">
|
689 |
+
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
|
690 |
+
<span>عقود ومناقصات</span>
|
691 |
+
<span>45%</span>
|
692 |
+
</div>
|
693 |
+
<div style="height: 10px; background-color: #e0e0e0; border-radius: 5px;">
|
694 |
+
<div style="height: 100%; width: 45%; background-color: #1E88E5; border-radius: 5px;"></div>
|
695 |
+
</div>
|
696 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
697 |
|
698 |
+
<div style="margin-bottom: 15px;">
|
699 |
+
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
|
700 |
+
<span>جداول كميات</span>
|
701 |
+
<span>30%</span>
|
702 |
+
</div>
|
703 |
+
<div style="height: 10px; background-color: #e0e0e0; border-radius: 5px;">
|
704 |
+
<div style="height: 100%; width: 30%; background-color: #43A047; border-radius: 5px;"></div>
|
705 |
+
</div>
|
706 |
+
</div>
|
707 |
|
708 |
+
<div style="margin-bottom: 15px;">
|
709 |
+
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
|
710 |
+
<span>شروط وأحكام</span>
|
711 |
+
<span>15%</span>
|
712 |
</div>
|
713 |
+
<div style="height: 10px; background-color: #e0e0e0; border-radius: 5px;">
|
714 |
+
<div style="height: 100%; width: 15%; background-color: #FB8C00; border-radius: 5px;"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
715 |
</div>
|
716 |
</div>
|
|
|
717 |
|
718 |
+
<div style="margin-bottom: 15px;">
|
719 |
+
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
|
720 |
+
<span>مستندات أخرى</span>
|
721 |
+
<span>10%</span>
|
722 |
+
</div>
|
723 |
+
<div style="height: 10px; background-color: #e0e0e0; border-radius: 5px;">
|
724 |
+
<div style="height: 100%; width: 10%; background-color: #78909C; border-radius: 5px;"></div>
|
725 |
+
</div>
|
726 |
+
</div>
|
727 |
+
</div>
|
|
|
|
|
|
|
|
|
728 |
""", unsafe_allow_html=True)
|
729 |
+
|
730 |
+
with col2:
|
731 |
st.markdown("""
|
732 |
+
<div style="padding: 20px; background-color: #f5f5f5; border-radius: 10px; height: 100%;">
|
733 |
+
<h4 style="color: #1E88E5; margin-bottom: 15px;">إحصائيات التحليل الشهرية</h4>
|
734 |
+
<div style="display: flex; justify-content: space-between; text-align: center;">
|
735 |
+
<div>
|
736 |
+
<div style="font-size: 2rem; font-weight: bold; color: #1E88E5;">42</div>
|
737 |
+
<div style="color: #666;">مستند تم تحليله</div>
|
738 |
+
</div>
|
739 |
+
<div>
|
740 |
+
<div style="font-size: 2rem; font-weight: bold; color: #43A047;">38</div>
|
741 |
+
<div style="color: #666;">تحليل ناجح</div>
|
742 |
+
</div>
|
743 |
+
<div>
|
744 |
+
<div style="font-size: 2rem; font-weight: bold; color: #FB8C00;">4</div>
|
745 |
+
<div style="color: #666;">تحليل غير مكتمل</div>
|
746 |
+
</div>
|
747 |
</div>
|
748 |
+
|
749 |
+
<h4 style="color: #1E88E5; margin-top: 20px; margin-bottom: 15px;">متوسط وقت المعالجة</h4>
|
750 |
+
<div style="display: flex; align-items: center; margin-bottom: 10px;">
|
751 |
+
<div style="width: 150px;">تحليل شامل:</div>
|
752 |
+
<div style="flex-grow: 1; height: 8px; background-color: #e0e0e0; border-radius: 5px;">
|
753 |
+
<div style="height: 100%; width: 80%; background-color: #1E88E5; border-radius: 5px;"></div>
|
754 |
+
</div>
|
755 |
+
<div style="width: 50px; text-align: left; padding-left: 10px;">2:30</div>
|
756 |
</div>
|
757 |
+
|
758 |
+
<div style="display: flex; align-items: center; margin-bottom: 10px;">
|
759 |
+
<div style="width: 150px;">جداول الكميات:</div>
|
760 |
+
<div style="flex-grow: 1; height: 8px; background-color: #e0e0e0; border-radius: 5px;">
|
761 |
+
<div style="height: 100%; width: 50%; background-color: #43A047; border-radius: 5px;"></div>
|
762 |
+
</div>
|
763 |
+
<div style="width: 50px; text-align: left; padding-left: 10px;">1:45</div>
|
764 |
</div>
|
765 |
+
|
766 |
+
<div style="display: flex; align-items: center;">
|
767 |
+
<div style="width: 150px;">الشروط والأحكام:</div>
|
768 |
+
<div style="flex-grow: 1; height: 8px; background-color: #e0e0e0; border-radius: 5px;">
|
769 |
+
<div style="height: 100%; width: 60%; background-color: #FB8C00; border-radius: 5px;"></div>
|
770 |
+
</div>
|
771 |
+
<div style="width: 50px; text-align: left; padding-left: 10px;">2:00</div>
|
772 |
</div>
|
773 |
</div>
|
774 |
""", unsafe_allow_html=True)
|
775 |
+
|
776 |
+
|
777 |
+
def render_reports_and_analytics():
|
778 |
+
"""عرض واجهة التقارير والتحليلات"""
|
779 |
+
st.markdown("<h1 class='app-title'>التقارير والتحليلات</h1>", unsafe_allow_html=True)
|
780 |
|
781 |
+
st.markdown("""
|
782 |
+
<div class="section-card">
|
783 |
+
<p>استخدم هذه الوحدة لإنشاء تقارير تحليلية متقدمة عن المشاريع والمناقصات والأداء العام.
|
784 |
+
يوفر النظام رؤى وتحليلات متعمقة تساعدك على فهم أداء مشاريعك وتحسين عمليات صنع القرار.</p>
|
785 |
+
</div>
|
786 |
+
""", unsafe_allow_html=True)
|
787 |
+
|
788 |
+
# أنواع التقارير
|
789 |
+
st.markdown("### أنواع التقارير")
|
790 |
+
|
791 |
+
col1, col2, col3 = st.columns(3)
|
792 |
+
|
793 |
+
with col1:
|
794 |
st.markdown("""
|
795 |
+
<div class="card">
|
796 |
+
<div class="card-title">تقارير المشاريع</div>
|
797 |
+
<p>تقارير تفصيلية عن حالة المشاريع وتقدمها ومؤشرات الأداء الرئيسية والمشكلات المحتملة.</p>
|
798 |
+
</div>
|
799 |
""", unsafe_allow_html=True)
|
800 |
|
801 |
+
if st.button("إنشاء تقرير", key="btn_project_report"):
|
802 |
+
# هنا سيتم استدعاء وحدة إنشاء تقارير المشاريع
|
803 |
+
st.session_state.report_type = "project"
|
804 |
+
st.session_state.show_report_form = True
|
805 |
+
st.experimental_rerun()
|
806 |
+
|
807 |
+
with col2:
|
808 |
st.markdown("""
|
809 |
+
<div class="card">
|
810 |
+
<div class="card-title">تقارير الأداء المالي</div>
|
811 |
+
<p>تحليل مالي للمشاريع يتضمن الإيرادات والتكاليف والأرباح والتدفقات النقدية والانحرافات عن الميزانية.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
812 |
</div>
|
813 |
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
814 |
|
815 |
+
if st.button("إنشاء تقرير", key="btn_financial_report"):
|
816 |
+
# هنا سيتم استدعاء وحدة إنشاء تقارير الأداء المالي
|
817 |
+
st.session_state.report_type = "financial"
|
818 |
+
st.session_state.show_report_form = True
|
819 |
+
st.experimental_rerun()
|
820 |
+
|
821 |
+
with col3:
|
822 |
st.markdown("""
|
823 |
+
<div class="card">
|
824 |
+
<div class="card-title">تقارير المناقصات</div>
|
825 |
+
<p>تحليل شامل للمناقصات النشطة والمنتهية ونسب الفوز والمنافسين ومقارنة الأسعار.</p>
|
826 |
+
</div>
|
827 |
""", unsafe_allow_html=True)
|
828 |
|
829 |
+
if st.button("إنشاء تقرير", key="btn_tender_report"):
|
830 |
+
# هنا سيتم استدعاء وحدة إنشاء تقارير المناقصات
|
831 |
+
st.session_state.report_type = "tender"
|
832 |
+
st.session_state.show_report_form = True
|
833 |
+
st.experimental_rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
834 |
|
835 |
+
# لوحة البيانات
|
836 |
+
st.markdown("### لوحة البيانات التنفيذية")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
837 |
|
838 |
+
col1, col2 = st.columns([2, 1])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
839 |
|
840 |
+
with col1:
|
841 |
+
st.markdown("#### أداء المشاريع حسب القطاع")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
842 |
|
843 |
+
# إنشاء بيانات تجريبية للرسم البياني
|
844 |
+
sectors = ['البنية التحتية', 'السكني', 'التعليمي', 'الصحي', 'النقل']
|
845 |
+
performance = [85, 72, 64, 90, 78]
|
846 |
|
847 |
+
# إنشاء رسم بياني شريطي
|
848 |
+
chart_data = pd.DataFrame({'القطاع': sectors, 'الأداء (%)': performance})
|
849 |
+
st.bar_chart(chart_data.set_index('القطاع'), use_container_width=True)
|
850 |
|
851 |
+
# عرض بيان توضيحي
|
852 |
+
st.caption("مقارنة أداء المشاريع عبر القطاعات المختلفة (نسبة الإنجاز)")
|
853 |
|
854 |
+
with col2:
|
855 |
+
st.markdown("#### المؤشرات الرئيسية")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
856 |
|
857 |
+
# نسبة المشاريع المتأخرة
|
858 |
+
st.markdown("##### نسبة المشاريع المتأخرة")
|
859 |
+
delayed_projects = 15
|
860 |
+
st.progress(delayed_projects / 100)
|
861 |
+
st.markdown(f"<p style='text-align: center; color: #F44336; font-weight: bold; margin-top: -10px;'>{delayed_projects}%</p>", unsafe_allow_html=True)
|
862 |
|
863 |
+
# متوسط هامش الربح
|
864 |
+
st.markdown("##### متوسط هامش الربح")
|
865 |
+
profit_margin = 22
|
866 |
+
st.progress(profit_margin / 100)
|
867 |
+
st.markdown(f"<p style='text-align: center; color: #4CAF50; font-weight: bold; margin-top: -10px;'>{profit_margin}%</p>", unsafe_allow_html=True)
|
868 |
|
869 |
+
# معدل نجاح المناقصات
|
870 |
+
st.markdown("##### معدل نجاح المناقصات")
|
871 |
+
tender_success = 35
|
872 |
+
st.progress(tender_success / 100)
|
873 |
+
st.markdown(f"<p style='text-align: center; color: #2196F3; font-weight: bold; margin-top: -10px;'>{tender_success}%</p>", unsafe_allow_html=True)
|
|
|
|
|
874 |
|
875 |
+
# تقارير الأداء
|
876 |
+
st.markdown("### تقارير الأداء الأخيرة")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
877 |
|
878 |
+
# التقرير الأول
|
879 |
+
with st.container():
|
880 |
+
col1, col2 = st.columns([3, 1])
|
881 |
|
882 |
+
with col1:
|
883 |
+
st.markdown("#### التقرير الشهري لمشاريع الربع الأول 2025")
|
884 |
+
st.markdown("تقرير شامل يوضح أداء جميع المشاريع النشطة خلال الربع الأول من عام 2025، بما في ذلك تحليل التكاليف والجدول الزمني والمخاطر.")
|
885 |
+
st.markdown("**تاريخ الإنشاء:** 15 مارس 2025")
|
886 |
|
887 |
+
with col2:
|
888 |
+
st.markdown("<br>", unsafe_allow_html=True) # إضافة مسافة
|
889 |
+
col2_1, col2_2 = st.columns(2)
|
890 |
+
with col2_1:
|
891 |
+
if st.button("عرض", key="view_report1"):
|
892 |
+
st.session_state.view_report = "quarterly_q1_2025"
|
893 |
+
st.session_state.show_report_viewer = True
|
894 |
+
with col2_2:
|
895 |
+
if st.button("تنزيل", key="download_report1"):
|
896 |
+
st.info("جاري تحميل التقرير...")
|
897 |
+
|
898 |
+
st.markdown("---")
|
899 |
+
|
900 |
+
# التقرير الثاني
|
901 |
+
with st.container():
|
902 |
+
col1, col2 = st.columns([3, 1])
|
903 |
|
904 |
+
with col1:
|
905 |
+
st.markdown("#### تحليل أداء المناقصات 2024-2025")
|
906 |
+
st.markdown("تحليل مقارن لنتائج المناقصات بين عامي 2024 و 2025، يوضح التحسن في معدلات النجاح وتحليل أسباب الخسارة وفرص التحسين.")
|
907 |
+
st.markdown("**تاريخ الإنشاء:** 28 فبراير 2025")
|
908 |
|
909 |
+
with col2:
|
910 |
+
st.markdown("<br>", unsafe_allow_html=True) # إضافة مسافة
|
911 |
+
col2_1, col2_2 = st.columns(2)
|
912 |
+
with col2_1:
|
913 |
+
if st.button("عرض", key="view_report2"):
|
914 |
+
st.session_state.view_report = "tenders_analysis_2024_2025"
|
915 |
+
st.session_state.show_report_viewer = True
|
916 |
+
with col2_2:
|
917 |
+
if st.button("تنزيل", key="download_report2"):
|
918 |
+
st.info("جاري تحميل التقرير...")
|
919 |
+
|
920 |
+
st.markdown("---")
|
921 |
+
|
922 |
+
# التقرير الثالث
|
923 |
+
with st.container():
|
924 |
+
col1, col2 = st.columns([3, 1])
|
925 |
|
926 |
+
with col1:
|
927 |
+
st.markdown("#### تقرير المخاطر المالية للمشاريع الجارية")
|
928 |
+
st.markdown("تقرير تفصيلي حول المخاطر المالية للمشاريع الجارية، بما في ذلك تحليل التدفقات النقدية والمستحقات المتأخرة والمطالبات المحتملة.")
|
929 |
+
st.markdown("**تاريخ الإنشاء:** 10 فبراير 2025")
|
930 |
|
931 |
+
with col2:
|
932 |
+
st.markdown("<br>", unsafe_allow_html=True) # إضافة مسافة
|
933 |
+
col2_1, col2_2 = st.columns(2)
|
934 |
+
with col2_1:
|
935 |
+
if st.button("عرض", key="view_report3"):
|
936 |
+
st.session_state.view_report = "financial_risks_2025"
|
937 |
+
st.session_state.show_report_viewer = True
|
938 |
+
with col2_2:
|
939 |
+
if st.button("تنزيل", key="download_report3"):
|
940 |
+
st.info("جاري تحميل التقرير...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
941 |
|
|
|
|
|
942 |
|
943 |
+
def render_ai_assistant():
|
944 |
+
"""عرض واجهة المساعد الذكي باستخدام المكون الجديد"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
945 |
try:
|
946 |
+
from modules.ai_assistant.assistant_app import AssistantApp
|
947 |
+
|
948 |
+
# عرض العنوان والوصف
|
949 |
+
st.markdown("<h1 class='app-title'>المساعد الذكي</h1>", unsafe_allow_html=True)
|
950 |
+
|
951 |
+
st.markdown("""
|
952 |
+
<div class="section-card">
|
953 |
+
<p>المساعد الذكي هو واجهة تفاعلية مدعومة بتقنيات الذكاء الاصطناعي لمساعدتك في جميع أنشطة إدارة المشاريع والعقود.
|
954 |
+
يمكنك طرح أسئلة بلغتك الطبيعية والحصول على إجابات فورية، أو طلب مساعدة في مهام محددة مثل تحليل بنود العقد أو تقدير التكاليف.</p>
|
|
|
|
|
|
|
955 |
</div>
|
956 |
+
""", unsafe_allow_html=True)
|
957 |
+
|
958 |
+
# استدعاء المساعد الذكي الجديد
|
959 |
+
ai_assistant = AssistantApp()
|
960 |
+
ai_assistant.render()
|
961 |
+
|
962 |
+
except Exception as e:
|
963 |
+
st.error(f"حدث خطأ في تحميل المساعد الذكي: {str(e)}")
|
964 |
+
st.markdown("""
|
965 |
+
<div class="error-card">
|
966 |
+
<h3>😞 عذراً، واجهنا مشكلة في تحميل المساعد الذكي</h3>
|
967 |
+
<p>يرجى المحاولة مرة أخرى لاحقاً أو التواصل مع فريق الدعم الفني إذا استمرت المشكلة.</p>
|
968 |
+
</div>
|
969 |
+
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
970 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
971 |
|
972 |
+
# تشغيل التطبيق عند تنفيذ الملف مباشرة
|
973 |
if __name__ == "__main__":
|
974 |
+
main()
|
database/db_connector.py
CHANGED
@@ -36,6 +36,26 @@ def get_connection():
|
|
36 |
except Exception as e:
|
37 |
print(f"خطأ في الاتصال بقاعدة البيانات: {e}")
|
38 |
|
39 |
-
# إذا فشل الاتصال، استخدم اتصال قاعدة بيانات
|
40 |
-
|
41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
except Exception as e:
|
37 |
print(f"خطأ في الاتصال بقاعدة البيانات: {e}")
|
38 |
|
39 |
+
# إذا فشل الاتصال، استخدم اتصال قاعدة بيانات SQLite محلية
|
40 |
+
import os
|
41 |
+
import sqlite3
|
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
|
modules/ai_assistant/ai_assistant.py
CHANGED
@@ -43,10 +43,9 @@ class AIAssistant:
|
|
43 |
# تهيئة مفتاح OpenAI API
|
44 |
self.openai_api_key = os.environ.get("OPENAI_API_KEY")
|
45 |
if self.openai_api_key:
|
46 |
-
|
47 |
self.is_api_available = True
|
48 |
else:
|
49 |
-
self.client = None
|
50 |
self.is_api_available = False
|
51 |
|
52 |
# نموذج OpenAI المستخدم
|
@@ -165,7 +164,7 @@ class AIAssistant:
|
|
165 |
if model is None:
|
166 |
model = self.model
|
167 |
|
168 |
-
response =
|
169 |
model=model,
|
170 |
messages=messages,
|
171 |
max_tokens=max_tokens,
|
@@ -175,10 +174,7 @@ class AIAssistant:
|
|
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,10 +184,8 @@ class AIAssistant:
|
|
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"
|
195 |
json=data,
|
196 |
timeout=60
|
197 |
)
|
@@ -204,3 +198,576 @@ class AIAssistant:
|
|
204 |
except Exception as e:
|
205 |
logging.error(f"خطأ في الاتصال بواجهة API الخلفية: {e}")
|
206 |
return {"error": f"خطأ في الاتصال بواجهة API الخلفية: {str(e)}"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
# تهيئة مفتاح OpenAI API
|
44 |
self.openai_api_key = os.environ.get("OPENAI_API_KEY")
|
45 |
if self.openai_api_key:
|
46 |
+
openai.api_key = self.openai_api_key
|
47 |
self.is_api_available = True
|
48 |
else:
|
|
|
49 |
self.is_api_available = False
|
50 |
|
51 |
# نموذج OpenAI المستخدم
|
|
|
164 |
if model is None:
|
165 |
model = self.model
|
166 |
|
167 |
+
response = openai.ChatCompletion.create(
|
168 |
model=model,
|
169 |
messages=messages,
|
170 |
max_tokens=max_tokens,
|
|
|
174 |
presence_penalty=0
|
175 |
)
|
176 |
|
177 |
+
return response
|
|
|
|
|
|
|
178 |
except Exception as e:
|
179 |
logging.error(f"خطأ في استدعاء OpenAI API: {e}")
|
180 |
return {
|
|
|
184 |
def _call_backend_api(self, endpoint, data):
|
185 |
"""استدعاء واجهة API الخلفية للنظام"""
|
186 |
try:
|
|
|
|
|
187 |
response = requests.post(
|
188 |
+
f"http://localhost:5000/api/{endpoint}",
|
189 |
json=data,
|
190 |
timeout=60
|
191 |
)
|
|
|
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()
|
modules/document_comparison/document_comparator.py
CHANGED
@@ -38,8 +38,37 @@ 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 |
# إعداد مقيم ROUGE لمقارنة النصوص
|
42 |
self.rouge_scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
|
44 |
def _preprocess_text(self, text):
|
45 |
"""معالجة النص قبل التحليل"""
|
|
|
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 |
"""معالجة النص قبل التحليل"""
|
modules/pricing/construction_calculator.py
CHANGED
@@ -19,6 +19,18 @@ def render_construction_calculator():
|
|
19 |
"""
|
20 |
عرض حاسبة تكاليف البناء المتكاملة
|
21 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
st.markdown("<h2 class='module-title'>حاسبة تكاليف البناء المتكاملة</h2>", unsafe_allow_html=True)
|
23 |
|
24 |
# معلومات المشروع
|
@@ -639,6 +651,30 @@ def render_final_report(project_name, project_location, project_area, project_ty
|
|
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,23 +717,39 @@ def render_final_report(project_name, project_location, project_area, project_ty
|
|
681 |
st.markdown("</div>", unsafe_allow_html=True)
|
682 |
|
683 |
# عرض التفاصيل بالمتر المربع
|
684 |
-
|
685 |
-
|
686 |
-
|
687 |
-
|
688 |
-
|
689 |
-
|
|
|
|
|
|
|
|
|
|
|
690 |
|
691 |
# رسم بياني لتوزيع التكاليف
|
692 |
st.markdown("<h4>توزيع التكاليف</h4>", unsafe_allow_html=True)
|
693 |
|
694 |
-
|
695 |
-
|
696 |
-
|
697 |
-
|
698 |
-
|
699 |
-
|
700 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
701 |
|
702 |
cost_df = pd.DataFrame(cost_distribution)
|
703 |
|
|
|
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 |
"""
|
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 |
st.markdown("</div>", unsafe_allow_html=True)
|
718 |
|
719 |
# عرض التفاصيل بالمتر المربع
|
720 |
+
if project_area > 0:
|
721 |
+
per_sqm_cost = total_price / project_area
|
722 |
+
st.markdown("<div class='card'>", unsafe_allow_html=True)
|
723 |
+
st.markdown("<h4>تكلفة المتر المربع</h4>", unsafe_allow_html=True)
|
724 |
+
st.markdown(f"<p>تكلفة المتر المربع الإجمالية: <strong>{per_sqm_cost:,.2f} ريال/م²</strong></p>", unsafe_allow_html=True)
|
725 |
+
st.markdown("</div>", unsafe_allow_html=True)
|
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 |
+
if total_price > 0:
|
737 |
+
cost_distribution = [
|
738 |
+
{"النوع": "المواد", "القيمة": st.session_state.materials_cost, "النسبة": st.session_state.materials_cost / total_price * 100},
|
739 |
+
{"النوع": "المعدات", "القيمة": st.session_state.equipment_cost, "النسبة": st.session_state.equipment_cost / total_price * 100},
|
740 |
+
{"النوع": "العمالة", "القيمة": st.session_state.labor_cost, "النسبة": st.session_state.labor_cost / total_price * 100},
|
741 |
+
{"النوع": "المصاريف الإدارية", "القيمة": st.session_state.admin_cost, "النسبة": st.session_state.admin_cost / total_price * 100},
|
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 |
|
modules/resources/resources_app.py
CHANGED
@@ -461,17 +461,21 @@ 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 |
|
476 |
def _render_materials_tab(self):
|
477 |
"""عرض تبويب المواد"""
|
@@ -488,7 +492,8 @@ class ResourcesApp:
|
|
488 |
category_filter = st.multiselect(
|
489 |
"تصفية حسب الفئة",
|
490 |
options=list(set(material['category'] for material in st.session_state.materials)),
|
491 |
-
default=[]
|
|
|
492 |
)
|
493 |
|
494 |
# تطبيق البحث والتصفية
|
@@ -614,7 +619,8 @@ class ResourcesApp:
|
|
614 |
category_filter = st.multiselect(
|
615 |
"تصفية حسب الفئة",
|
616 |
options=list(set(labor['category'] for labor in st.session_state.labor)),
|
617 |
-
default=[]
|
|
|
618 |
)
|
619 |
|
620 |
# تطبيق البحث والتصفية
|
@@ -740,7 +746,8 @@ class ResourcesApp:
|
|
740 |
category_filter = st.multiselect(
|
741 |
"تصفية حسب الفئة",
|
742 |
options=list(set(equipment['category'] for equipment in st.session_state.equipment)),
|
743 |
-
default=[]
|
|
|
744 |
)
|
745 |
|
746 |
# تطبيق البحث والتصفية
|
@@ -868,14 +875,16 @@ class ResourcesApp:
|
|
868 |
category_filter = st.multiselect(
|
869 |
"تصفية حسب الفئة",
|
870 |
options=list(set(subcontractor['category'] for subcontractor in st.session_state.subcontractors)),
|
871 |
-
default=[]
|
|
|
872 |
)
|
873 |
|
874 |
with col3:
|
875 |
city_filter = st.multiselect(
|
876 |
"تصفية حسب المدينة",
|
877 |
options=list(set(subcontractor['city'] for subcontractor in st.session_state.subcontractors)),
|
878 |
-
default=[]
|
|
|
879 |
)
|
880 |
|
881 |
# تطبيق البحث والتصفية
|
@@ -1038,7 +1047,8 @@ class ResourcesApp:
|
|
1038 |
selected_materials = st.multiselect(
|
1039 |
"اختر المواد للتحليل",
|
1040 |
options=material_options,
|
1041 |
-
default=material_options[:3] if len(material_options) >= 3 else material_options
|
|
|
1042 |
)
|
1043 |
|
1044 |
if not selected_materials:
|
@@ -1049,15 +1059,30 @@ class ResourcesApp:
|
|
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 |
-
|
1057 |
-
|
1058 |
-
|
1059 |
-
|
1060 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1061 |
|
1062 |
if not price_history_data:
|
1063 |
st.warning("لا توجد بيانات أسعار متاحة للمواد المختارة.")
|
@@ -1066,32 +1091,37 @@ class ResourcesApp:
|
|
1066 |
# تحويل البيانات إلى DataFrame
|
1067 |
price_history_df = pd.DataFrame(price_history_data)
|
1068 |
|
1069 |
-
#
|
1070 |
-
|
1071 |
-
|
1072 |
-
|
1073 |
-
|
1074 |
-
|
1075 |
-
|
1076 |
-
|
1077 |
-
|
1078 |
-
|
1079 |
-
|
|
|
|
|
|
|
|
|
1080 |
|
1081 |
# حساب التغيرات في الأسعار
|
1082 |
materials_price_changes = []
|
1083 |
|
1084 |
for material_name in selected_materials:
|
1085 |
-
|
|
|
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['
|
1095 |
|
1096 |
materials_price_changes.append({
|
1097 |
'المادة': material_name,
|
@@ -1259,17 +1289,21 @@ class ResourcesApp:
|
|
1259 |
price_history_data = []
|
1260 |
for entry in st.session_state.price_history:
|
1261 |
if entry['material_id'] == material_id:
|
1262 |
-
|
1263 |
-
|
1264 |
-
|
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,7 +1312,7 @@ class ResourcesApp:
|
|
1278 |
# حساب متوسط التغير الشهري
|
1279 |
monthly_changes = []
|
1280 |
for i in range(1, len(price_history_df)):
|
1281 |
-
monthly_changes.append(price_history_df.iloc[i]['
|
1282 |
|
1283 |
if monthly_changes:
|
1284 |
avg_monthly_change = sum(monthly_changes) / len(monthly_changes)
|
@@ -1286,8 +1320,8 @@ class ResourcesApp:
|
|
1286 |
avg_monthly_change = 0
|
1287 |
|
1288 |
# إنشاء بيانات التوقع
|
1289 |
-
last_date = price_history_df['
|
1290 |
-
last_price = price_history_df.loc[price_history_df['
|
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,25 +1330,25 @@ class ResourcesApp:
|
|
1296 |
forecast_prices = [price + random.uniform(-price*0.05, price*0.05) for price in forecast_prices]
|
1297 |
|
1298 |
forecast_df = pd.DataFrame({
|
1299 |
-
'
|
1300 |
-
'
|
1301 |
-
'
|
1302 |
})
|
1303 |
|
1304 |
# دمج البيانات التاريخية والتوقع
|
1305 |
historical_df = price_history_df.copy()
|
1306 |
-
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,9 +1383,14 @@ class ResourcesApp:
|
|
1349 |
st.markdown("#### جدول توقع الأسعار")
|
1350 |
|
1351 |
forecast_table = forecast_df.copy()
|
1352 |
-
forecast_table['
|
1353 |
-
forecast_table['
|
1354 |
-
|
|
|
|
|
|
|
|
|
|
|
1355 |
|
1356 |
st.dataframe(forecast_table, use_container_width=True, hide_index=True)
|
1357 |
|
|
|
461 |
# تحويل البيانات إلى DataFrame
|
462 |
price_history_df = pd.DataFrame(price_history_data)
|
463 |
|
464 |
+
# التحقق من وجود بيانات قبل رسم المخطط
|
465 |
+
if len(price_history_data) == 0:
|
466 |
+
st.warning("لا توجد بيانات تاريخية للأسعار متاحة لعرضها")
|
467 |
+
else:
|
468 |
+
# رسم المخطط الخطي
|
469 |
+
fig = px.line(
|
470 |
+
price_history_df,
|
471 |
+
x='التاريخ',
|
472 |
+
y='السعر',
|
473 |
+
color='المادة',
|
474 |
+
title='تطور أسعار المواد الرئيسية خلال العام الماضي',
|
475 |
+
labels={'التاريخ': 'التاريخ', 'السعر': 'السعر (ريال)', 'المادة': 'المادة'}
|
476 |
+
)
|
477 |
+
# عرض المخطط فقط إذا تم إنشاؤه
|
478 |
+
st.plotly_chart(fig, use_container_width=True)
|
479 |
|
480 |
def _render_materials_tab(self):
|
481 |
"""عرض تبويب المواد"""
|
|
|
492 |
category_filter = st.multiselect(
|
493 |
"تصفية حسب الفئة",
|
494 |
options=list(set(material['category'] for material in st.session_state.materials)),
|
495 |
+
default=[],
|
496 |
+
key="material_category_filter_tab"
|
497 |
)
|
498 |
|
499 |
# تطبيق البحث والتصفية
|
|
|
619 |
category_filter = st.multiselect(
|
620 |
"تصفية حسب الفئة",
|
621 |
options=list(set(labor['category'] for labor in st.session_state.labor)),
|
622 |
+
default=[],
|
623 |
+
key="labor_category_filter_tab"
|
624 |
)
|
625 |
|
626 |
# تطبيق البحث والتصفية
|
|
|
746 |
category_filter = st.multiselect(
|
747 |
"تصفية حسب الفئة",
|
748 |
options=list(set(equipment['category'] for equipment in st.session_state.equipment)),
|
749 |
+
default=[],
|
750 |
+
key="equipment_category_filter_tab"
|
751 |
)
|
752 |
|
753 |
# تطبيق البحث والتصفية
|
|
|
875 |
category_filter = st.multiselect(
|
876 |
"تصفية حسب الفئة",
|
877 |
options=list(set(subcontractor['category'] for subcontractor in st.session_state.subcontractors)),
|
878 |
+
default=[],
|
879 |
+
key="subcontractor_category_filter_tab"
|
880 |
)
|
881 |
|
882 |
with col3:
|
883 |
city_filter = st.multiselect(
|
884 |
"تصفية حسب المدينة",
|
885 |
options=list(set(subcontractor['city'] for subcontractor in st.session_state.subcontractors)),
|
886 |
+
default=[],
|
887 |
+
key="subcontractor_city_filter_tab"
|
888 |
)
|
889 |
|
890 |
# تطبيق البحث والتصفية
|
|
|
1047 |
selected_materials = st.multiselect(
|
1048 |
"اختر المواد للتحليل",
|
1049 |
options=material_options,
|
1050 |
+
default=material_options[:3] if len(material_options) >= 3 else material_options,
|
1051 |
+
key="price_analysis_materials_tab"
|
1052 |
)
|
1053 |
|
1054 |
if not selected_materials:
|
|
|
1059 |
material_ids = {material['name']: material['id'] for material in st.session_state.materials}
|
1060 |
selected_ids = [material_ids[name] for name in selected_materials if name in material_ids]
|
1061 |
|
1062 |
+
# التحقق من وجود بيانات سعرية في session_state.price_history
|
1063 |
+
if 'price_history' not in st.session_state or not st.session_state.price_history:
|
1064 |
+
st.warning("لا توجد بيانات أسعار متاحة للتحليل.")
|
1065 |
+
return
|
1066 |
+
|
1067 |
price_history_data = []
|
1068 |
for entry in st.session_state.price_history:
|
1069 |
if entry['material_id'] in selected_ids:
|
1070 |
+
# الحصول على اسم المادة من المعرف
|
1071 |
material_name = next((material['name'] for material in st.session_state.materials if material['id'] == entry['material_id']), "")
|
1072 |
+
|
1073 |
+
# التحقق من وجود المفاتيح المطلوبة
|
1074 |
+
if 'date' in entry and 'price' in entry:
|
1075 |
+
try:
|
1076 |
+
# إضافة البيانات إلى قائمة البيانات مع تحويل التاريخ إلى كائن datetime
|
1077 |
+
price_history_data.append({
|
1078 |
+
'material': material_name, # استخدام أسماء إنجليزية للمفاتيح
|
1079 |
+
'date': pd.to_datetime(entry['date']),
|
1080 |
+
'price': float(entry['price']) # التأكد من تحويل السعر إلى رقم
|
1081 |
+
})
|
1082 |
+
except (ValueError, TypeError) as e:
|
1083 |
+
# تسجيل أخطاء تحويل البيانات
|
1084 |
+
st.error(f"خطأ في معالجة البيانات: {e}")
|
1085 |
+
continue
|
1086 |
|
1087 |
if not price_history_data:
|
1088 |
st.warning("لا توجد بيانات أسعار متاحة للمواد المختارة.")
|
|
|
1091 |
# تحويل البيانات إلى DataFrame
|
1092 |
price_history_df = pd.DataFrame(price_history_data)
|
1093 |
|
1094 |
+
# التحقق من وجود بيانات قبل رسم المخطط
|
1095 |
+
if len(price_history_df) == 0:
|
1096 |
+
st.warning("لا توجد بيانات تاريخية للأسعار متاحة لعرضها")
|
1097 |
+
else:
|
1098 |
+
# عرض المخطط الخطي للأسعار باستخدام أسماء الأعمدة الإنجليزية
|
1099 |
+
fig = px.line(
|
1100 |
+
price_history_df,
|
1101 |
+
x='date',
|
1102 |
+
y='price',
|
1103 |
+
color='material',
|
1104 |
+
title='تطور أسعار المواد المختارة',
|
1105 |
+
labels={'date': 'التاريخ', 'price': 'السعر (ريال)', 'material': 'المادة'}
|
1106 |
+
)
|
1107 |
+
|
1108 |
+
st.plotly_chart(fig, use_container_width=True)
|
1109 |
|
1110 |
# حساب التغيرات في الأسعار
|
1111 |
materials_price_changes = []
|
1112 |
|
1113 |
for material_name in selected_materials:
|
1114 |
+
# استخدام أسماء الأعمدة الإنجليزية للتصفية والترتيب
|
1115 |
+
material_prices = price_history_df[price_history_df['material'] == material_name].sort_values('date')
|
1116 |
|
1117 |
if len(material_prices) >= 2:
|
1118 |
+
first_price = material_prices.iloc[0]['price']
|
1119 |
+
last_price = material_prices.iloc[-1]['price']
|
1120 |
price_change = last_price - first_price
|
1121 |
price_change_percent = (price_change / first_price) * 100
|
1122 |
|
1123 |
# حساب التقلب (الانحراف المعياري)
|
1124 |
+
price_volatility = material_prices['price'].std()
|
1125 |
|
1126 |
materials_price_changes.append({
|
1127 |
'المادة': material_name,
|
|
|
1289 |
price_history_data = []
|
1290 |
for entry in st.session_state.price_history:
|
1291 |
if entry['material_id'] == material_id:
|
1292 |
+
try:
|
1293 |
+
price_history_data.append({
|
1294 |
+
'date': pd.to_datetime(entry['date']),
|
1295 |
+
'price': float(entry['price'])
|
1296 |
+
})
|
1297 |
+
except (ValueError, TypeError) as e:
|
1298 |
+
st.error(f"خطأ في معالجة البيانات: {e}")
|
1299 |
+
continue
|
1300 |
|
1301 |
if not price_history_data:
|
1302 |
st.warning("لا توجد بيانات تاريخية كافية للمادة المحددة للقيام بالتوقع.")
|
1303 |
return
|
1304 |
|
1305 |
# تحويل البيانات إلى DataFrame
|
1306 |
+
price_history_df = pd.DataFrame(price_history_data).sort_values('date')
|
1307 |
|
1308 |
# إجراء التوقع
|
1309 |
# في الواقع، ستستخدم نماذج تعلم آلي مثل ARIMA أو Prophet
|
|
|
1312 |
# حساب متوسط التغير الشهري
|
1313 |
monthly_changes = []
|
1314 |
for i in range(1, len(price_history_df)):
|
1315 |
+
monthly_changes.append(price_history_df.iloc[i]['price'] - price_history_df.iloc[i-1]['price'])
|
1316 |
|
1317 |
if monthly_changes:
|
1318 |
avg_monthly_change = sum(monthly_changes) / len(monthly_changes)
|
|
|
1320 |
avg_monthly_change = 0
|
1321 |
|
1322 |
# إنشاء بيانات التوقع
|
1323 |
+
last_date = price_history_df['date'].max()
|
1324 |
+
last_price = price_history_df.loc[price_history_df['date'] == last_date, 'price'].values[0]
|
1325 |
|
1326 |
forecast_dates = pd.date_range(start=last_date + pd.DateOffset(months=1), periods=forecast_period, freq='M')
|
1327 |
forecast_prices = [last_price + (i+1) * avg_monthly_change for i in range(forecast_period)]
|
|
|
1330 |
forecast_prices = [price + random.uniform(-price*0.05, price*0.05) for price in forecast_prices]
|
1331 |
|
1332 |
forecast_df = pd.DataFrame({
|
1333 |
+
'date': forecast_dates,
|
1334 |
+
'price': forecast_prices,
|
1335 |
+
'type': ['توقع'] * forecast_period
|
1336 |
})
|
1337 |
|
1338 |
# دمج البيانات التاريخية والتوقع
|
1339 |
historical_df = price_history_df.copy()
|
1340 |
+
historical_df['type'] = ['تاريخي'] * len(historical_df)
|
1341 |
|
1342 |
combined_df = pd.concat([historical_df, forecast_df], ignore_index=True)
|
1343 |
|
1344 |
# عرض المخطط
|
1345 |
fig = px.line(
|
1346 |
combined_df,
|
1347 |
+
x='date',
|
1348 |
+
y='price',
|
1349 |
+
color='type',
|
1350 |
title=f'توقع أسعار {selected_material} للـ {forecast_period} أشهر القادمة',
|
1351 |
+
labels={'date': 'التاريخ', 'price': 'السعر (ريال)', 'type': 'النوع'},
|
1352 |
color_discrete_map={'تاريخي': 'blue', 'توقع': 'red'}
|
1353 |
)
|
1354 |
|
|
|
1383 |
st.markdown("#### جدول توقع الأسعار")
|
1384 |
|
1385 |
forecast_table = forecast_df.copy()
|
1386 |
+
forecast_table['date'] = forecast_table['date'].dt.strftime('%Y-%m')
|
1387 |
+
forecast_table['price'] = forecast_table['price'].apply(lambda x: f"{x:,.2f} ريال")
|
1388 |
+
# إعادة تسمية الأعمدة إلى العربية لعرض الجدول
|
1389 |
+
forecast_table = forecast_table.rename(columns={
|
1390 |
+
'date': 'التاريخ',
|
1391 |
+
'price': 'السعر'
|
1392 |
+
})
|
1393 |
+
forecast_table = forecast_table.drop(columns=['type'])
|
1394 |
|
1395 |
st.dataframe(forecast_table, use_container_width=True, hide_index=True)
|
1396 |
|
modules/risk_assessment/contract_risk_analyzer.py
CHANGED
@@ -1251,30 +1251,35 @@ class ContractRiskAnalyzer:
|
|
1251 |
chart_data.append({"category": data["name_ar"], "severity": "متوسطة", "count": data["medium"]})
|
1252 |
chart_data.append({"category": data["name_ar"], "severity": "منخفضة", "count": data["low"]})
|
1253 |
|
1254 |
-
|
1255 |
-
|
1256 |
-
|
1257 |
-
|
1258 |
-
|
1259 |
-
|
1260 |
-
|
1261 |
-
|
1262 |
-
|
1263 |
-
|
1264 |
-
|
1265 |
-
|
1266 |
-
|
1267 |
-
|
1268 |
-
|
1269 |
-
|
1270 |
-
|
1271 |
-
|
1272 |
-
|
1273 |
-
|
1274 |
-
|
1275 |
-
|
1276 |
-
|
1277 |
-
|
|
|
|
|
|
|
|
|
|
|
1278 |
|
1279 |
# عرض مخطط دائري لتوزيع مستويات الخطورة
|
1280 |
col1, col2 = st.columns([1, 1])
|
@@ -1665,61 +1670,66 @@ class ContractRiskAnalyzer:
|
|
1665 |
"total": data["contract2"]["total"]
|
1666 |
})
|
1667 |
|
1668 |
-
|
1669 |
-
|
1670 |
-
|
1671 |
-
|
1672 |
-
|
1673 |
-
|
1674 |
-
|
1675 |
-
|
1676 |
-
|
1677 |
-
|
1678 |
-
|
1679 |
-
|
1680 |
-
|
1681 |
-
|
1682 |
-
|
1683 |
-
|
1684 |
-
|
1685 |
-
|
1686 |
-
|
1687 |
-
|
1688 |
-
|
1689 |
-
|
1690 |
-
|
1691 |
-
|
1692 |
-
|
1693 |
-
|
1694 |
-
|
1695 |
-
|
1696 |
-
|
1697 |
-
|
1698 |
-
|
1699 |
-
|
1700 |
-
|
1701 |
-
|
1702 |
-
|
1703 |
-
|
1704 |
-
|
1705 |
-
|
1706 |
-
|
1707 |
-
|
1708 |
-
|
1709 |
-
|
1710 |
-
|
1711 |
-
|
1712 |
-
|
1713 |
-
|
1714 |
-
|
1715 |
-
|
1716 |
-
|
1717 |
-
|
1718 |
-
|
1719 |
-
|
1720 |
-
|
1721 |
-
|
1722 |
-
|
|
|
|
|
|
|
|
|
|
|
1723 |
|
1724 |
# عرض جدول مقارنة المخاطر المشتركة
|
1725 |
st.markdown("<h3>المخاطر المشتركة بين العقدين</h3>", unsafe_allow_html=True)
|
|
|
1251 |
chart_data.append({"category": data["name_ar"], "severity": "متوسطة", "count": data["medium"]})
|
1252 |
chart_data.append({"category": data["name_ar"], "severity": "منخفضة", "count": data["low"]})
|
1253 |
|
1254 |
+
# التحقق من وجود بيانات قبل إنشاء الرسم البياني
|
1255 |
+
if chart_data:
|
1256 |
+
chart_df = pd.DataFrame(chart_data)
|
1257 |
+
|
1258 |
+
# إنشاء مخطط باستخدام plotly
|
1259 |
+
fig = px.bar(
|
1260 |
+
chart_df,
|
1261 |
+
x="category",
|
1262 |
+
y="count",
|
1263 |
+
color="severity",
|
1264 |
+
title="توزيع المخاطر حسب الفئة والخطورة",
|
1265 |
+
labels={"category": "فئة المخاطر", "count": "عدد المخاطر", "severity": "مستوى الخطورة"},
|
1266 |
+
color_discrete_map={"عالية": "#d63031", "متوسطة": "#fdcb6e", "منخفضة": "#00b894"}
|
1267 |
+
)
|
1268 |
+
|
1269 |
+
# تنسيق المخطط
|
1270 |
+
fig.update_layout(
|
1271 |
+
barmode='stack',
|
1272 |
+
xaxis={'categoryorder': 'total descending'},
|
1273 |
+
direction='rtl',
|
1274 |
+
font=dict(family="Arial, sans-serif", size=14),
|
1275 |
+
height=400,
|
1276 |
+
margin=dict(l=10, r=10, t=50, b=10)
|
1277 |
+
)
|
1278 |
+
|
1279 |
+
st.plotly_chart(fig, use_container_width=True)
|
1280 |
+
else:
|
1281 |
+
# إذا لم تكن هناك بيانات، عرض رسالة بديلة
|
1282 |
+
st.info("لم يتم العثور على مخاطر كافية لعرض الرسم البياني")
|
1283 |
|
1284 |
# عرض مخطط دائري لتوزيع مستويات الخطورة
|
1285 |
col1, col2 = st.columns([1, 1])
|
|
|
1670 |
"total": data["contract2"]["total"]
|
1671 |
})
|
1672 |
|
1673 |
+
# التحقق من وجود بيانات قبل إنشاء الرسم البياني
|
1674 |
+
if chart_data:
|
1675 |
+
chart_df = pd.DataFrame(chart_data)
|
1676 |
+
|
1677 |
+
# إنشاء مخطط شريطي مقارن
|
1678 |
+
fig = go.Figure()
|
1679 |
+
|
1680 |
+
for contract in [title1, title2]:
|
1681 |
+
contract_data = chart_df[chart_df["contract"] == contract]
|
1682 |
+
fig.add_trace(go.Bar(
|
1683 |
+
x=contract_data["category"],
|
1684 |
+
y=contract_data["total"],
|
1685 |
+
name=contract,
|
1686 |
+
text=contract_data["total"],
|
1687 |
+
textposition="auto"
|
1688 |
+
))
|
1689 |
+
|
1690 |
+
fig.update_layout(
|
1691 |
+
title="مقارنة إجمالي المخاطر حسب الفئة",
|
1692 |
+
xaxis_title="فئة المخاطر",
|
1693 |
+
yaxis_title="عدد المخاطر",
|
1694 |
+
barmode='group',
|
1695 |
+
xaxis={'categoryorder': 'total descending'},
|
1696 |
+
direction='rtl',
|
1697 |
+
font=dict(family="Arial, sans-serif", size=14),
|
1698 |
+
height=500,
|
1699 |
+
margin=dict(l=10, r=10, t=50, b=10)
|
1700 |
+
)
|
1701 |
+
|
1702 |
+
st.plotly_chart(fig, use_container_width=True)
|
1703 |
+
|
1704 |
+
# عرض مخطط مقارنة المخاطر العالية
|
1705 |
+
fig = go.Figure()
|
1706 |
+
|
1707 |
+
for contract in [title1, title2]:
|
1708 |
+
contract_data = chart_df[chart_df["contract"] == contract]
|
1709 |
+
fig.add_trace(go.Bar(
|
1710 |
+
x=contract_data["category"],
|
1711 |
+
y=contract_data["high"],
|
1712 |
+
name=contract,
|
1713 |
+
text=contract_data["high"],
|
1714 |
+
textposition="auto",
|
1715 |
+
marker_color="#d63031" if contract == title1 else "#ff7979"
|
1716 |
+
))
|
1717 |
+
|
1718 |
+
fig.update_layout(
|
1719 |
+
title="مقارنة المخاطر العالية حسب الفئة",
|
1720 |
+
xaxis_title="فئة المخاطر",
|
1721 |
+
yaxis_title="عدد المخاطر العالية",
|
1722 |
+
barmode='group',
|
1723 |
+
xaxis={'categoryorder': 'total descending'},
|
1724 |
+
direction='rtl',
|
1725 |
+
font=dict(family="Arial, sans-serif", size=14),
|
1726 |
+
height=400
|
1727 |
+
)
|
1728 |
+
|
1729 |
+
st.plotly_chart(fig, use_container_width=True)
|
1730 |
+
else:
|
1731 |
+
# إذا لم تكن هناك بيانات، عرض رسالة بديلة
|
1732 |
+
st.info("لم يتم العثور على بيانات كافية للمقارنة وعرض الرسم البياني")
|
1733 |
|
1734 |
# عرض جدول مقارنة المخاطر المشتركة
|
1735 |
st.markdown("<h3>المخاطر المشتركة بين العقدين</h3>", unsafe_allow_html=True)
|
static/css/rtl-fixes.css
CHANGED
@@ -1,57 +1,66 @@
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* إصلاحات لدعم اللغة العربية والعرض من اليمين إلى اليسار */
|
2 |
+
|
3 |
+
/* نقل الشريط الجانبي من اليسار إلى اليمين */
|
4 |
+
[data-testid="stSidebar"] {
|
5 |
+
right: 0;
|
6 |
+
left: unset !important;
|
7 |
+
}
|
8 |
+
|
9 |
+
section[data-testid="stSidebarContent"] {
|
10 |
+
direction: rtl !important;
|
11 |
+
text-align: right !important;
|
12 |
+
}
|
13 |
+
|
14 |
+
/* تعديل الاتجاه للمحتوى الرئيسي */
|
15 |
+
.main .block-container {
|
16 |
+
direction: rtl;
|
17 |
+
text-align: right;
|
18 |
+
margin-left: 0;
|
19 |
+
margin-right: 21rem;
|
20 |
+
max-width: calc(100% - 21rem);
|
21 |
+
}
|
22 |
+
|
23 |
+
@media (max-width: 768px) {
|
24 |
+
.main .block-container {
|
25 |
+
margin-right: 0;
|
26 |
+
max-width: 100%;
|
27 |
+
}
|
28 |
+
}
|
29 |
+
|
30 |
+
/* تصحيح اتجاه النصوص والعناصر */
|
31 |
+
.streamlit-expanderHeader,
|
32 |
+
.stRadio > div,
|
33 |
+
.stCheckbox > div,
|
34 |
+
.stSelectbox > div,
|
35 |
+
.stTextInput > div {
|
36 |
+
direction: rtl;
|
37 |
+
text-align: right;
|
38 |
+
}
|
39 |
+
|
40 |
+
/* تعديل قائمة الخيارات في streamlit_option_menu */
|
41 |
+
.nav-link {
|
42 |
+
text-align: right !important;
|
43 |
+
}
|
44 |
+
|
45 |
+
/* تحسين ظهور الجداول */
|
46 |
+
[data-testid="stTable"] {
|
47 |
+
direction: rtl;
|
48 |
+
}
|
49 |
+
|
50 |
+
/* تصحيح أزرار الأرقام والتواريخ */
|
51 |
+
.stNumberInput [data-baseweb="input"],
|
52 |
+
.stDateInput [data-baseweb="input"] {
|
53 |
+
direction: ltr;
|
54 |
+
}
|
55 |
+
|
56 |
+
/* جعل عناصر الملاحظات والخانات النصية تدعم العربية */
|
57 |
+
[data-testid="stMarkdown"], textarea {
|
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 |
+
}
|
utils/assets/logo.svg
ADDED
|
utils/components/header.py
CHANGED
@@ -7,18 +7,26 @@ from datetime import datetime
|
|
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
|
22 |
<p>الحلول الشاملة للتسعير والتحليل بالذكاء الاصطناعي - شركة شبه الجزيرة للمقاولات</p>
|
23 |
</div>
|
24 |
<div class="header-info">
|
@@ -44,7 +52,7 @@ def render_header():
|
|
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)
|
|
|
7 |
import config
|
8 |
|
9 |
|
10 |
+
def render_header(page_title=None):
|
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>{title}</h1>
|
30 |
<p>الحلول الشاملة للتسعير والتحليل بالذكاء الاصطناعي - شركة شبه الجزيرة للمقاولات</p>
|
31 |
</div>
|
32 |
<div class="header-info">
|
|
|
52 |
year = today.year
|
53 |
|
54 |
# استبدال القيم في قالب HTML
|
55 |
+
header_html = header_html.format(title=title_display, day=day, month=month, year=year)
|
56 |
|
57 |
# عرض الترويسة
|
58 |
st.markdown(header_html, unsafe_allow_html=True)
|
utils/components/sidebar.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1 |
"""
|
2 |
-
مكون الشريط الجانبي
|
3 |
"""
|
4 |
|
5 |
import streamlit as st
|
6 |
from datetime import datetime
|
|
|
7 |
import config
|
8 |
from streamlit_option_menu import option_menu
|
9 |
|
@@ -16,11 +17,45 @@ def render_sidebar():
|
|
16 |
اسم الوحدة المحددة
|
17 |
"""
|
18 |
with st.sidebar:
|
19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
|
21 |
-
# إنشاء قائمة الخيارات باستخدام مكتبة streamlit_option_menu
|
22 |
selected_module = option_menu(
|
23 |
-
"
|
24 |
[
|
25 |
"الرئيسية",
|
26 |
"إدارة المشاريع",
|
@@ -58,10 +93,40 @@ def render_sidebar():
|
|
58 |
menu_icon="cast",
|
59 |
default_index=0,
|
60 |
styles={
|
61 |
-
"container": {
|
62 |
-
|
63 |
-
|
64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
}
|
66 |
)
|
67 |
|
|
|
1 |
"""
|
2 |
+
مكون الشريط الجانبي - تصميم محسن مع دعم كامل للغة العربية
|
3 |
"""
|
4 |
|
5 |
import streamlit as st
|
6 |
from datetime import datetime
|
7 |
+
import os
|
8 |
import config
|
9 |
from streamlit_option_menu import option_menu
|
10 |
|
|
|
17 |
اسم الوحدة المحددة
|
18 |
"""
|
19 |
with st.sidebar:
|
20 |
+
# تطبيق الأنماط المحسنة على الشريط الجانبي
|
21 |
+
st.markdown("""
|
22 |
+
<style>
|
23 |
+
.sidebar .sidebar-content {
|
24 |
+
background: linear-gradient(135deg, #0B6E4F 0%, #08603a 100%);
|
25 |
+
}
|
26 |
+
/* المزيد من التنسيقات المخصصة للشريط الجانبي */
|
27 |
+
.sidebar .block-container {
|
28 |
+
padding-top: 0rem;
|
29 |
+
}
|
30 |
+
div[data-testid="stSidebarNav"] > div:first-child {
|
31 |
+
margin-top: -2rem;
|
32 |
+
}
|
33 |
+
</style>
|
34 |
+
""", unsafe_allow_html=True)
|
35 |
+
|
36 |
+
# عرض الشعار المحسن
|
37 |
+
logo_path = "utils/assets/logo.svg"
|
38 |
+
if os.path.exists(logo_path):
|
39 |
+
st.image(logo_path, width=180, use_column_width=False, output_format="SVG", clamp=False)
|
40 |
+
else:
|
41 |
+
# احتياطي إذا لم يتم العثور على الشعار
|
42 |
+
st.markdown("""
|
43 |
+
<div style="text-align: center; margin-bottom: 2rem;">
|
44 |
+
<div style="font-size: 2rem; font-weight: bold; color: white; margin-bottom: 0.5rem;">WAHBI</div>
|
45 |
+
<div style="font-size: 1rem; color: rgba(255,255,255,0.8);">نظام العقود الذكي</div>
|
46 |
+
</div>
|
47 |
+
""", unsafe_allow_html=True)
|
48 |
+
|
49 |
+
# إضافة العنوان قبل القائمة
|
50 |
+
st.markdown("""
|
51 |
+
<div class="app-title" style="text-align: center; margin-bottom: 1.5rem; color: white; font-size: 1.3rem; font-weight: bold;">
|
52 |
+
نظام تحليل العقود والمناقصات
|
53 |
+
</div>
|
54 |
+
""", unsafe_allow_html=True)
|
55 |
|
56 |
+
# إنشاء قائمة الخيارات باستخدام مكتبة streamlit_option_menu بتصميم محسن
|
57 |
selected_module = option_menu(
|
58 |
+
"", # إزالة العنوان لأننا أضفناه فوق القائمة
|
59 |
[
|
60 |
"الرئيسية",
|
61 |
"إدارة المشاريع",
|
|
|
93 |
menu_icon="cast",
|
94 |
default_index=0,
|
95 |
styles={
|
96 |
+
"container": {
|
97 |
+
"padding": "0px",
|
98 |
+
"background-color": "transparent",
|
99 |
+
"direction": "rtl",
|
100 |
+
"border-radius": "10px",
|
101 |
+
"margin-bottom": "20px"
|
102 |
+
},
|
103 |
+
"icon": {
|
104 |
+
"color": "#FFB100",
|
105 |
+
"font-size": "16px",
|
106 |
+
"margin-left": "10px",
|
107 |
+
"border-radius": "5px"
|
108 |
+
},
|
109 |
+
"nav-link": {
|
110 |
+
"font-size": "15px",
|
111 |
+
"text-align": "right",
|
112 |
+
"margin": "0px 0px 5px 0px",
|
113 |
+
"padding": "10px 15px",
|
114 |
+
"direction": "rtl",
|
115 |
+
"justify-content": "flex-end",
|
116 |
+
"border-radius": "7px",
|
117 |
+
"color": "rgba(255, 255, 255, 0.8)",
|
118 |
+
"font-weight": "500",
|
119 |
+
"transition": "all 0.3s ease"
|
120 |
+
},
|
121 |
+
"nav-link-selected": {
|
122 |
+
"background-color": "rgba(255, 255, 255, 0.1)",
|
123 |
+
"color": "#ffffff",
|
124 |
+
"font-weight": "700",
|
125 |
+
"border-right": "3px solid #FFB100"
|
126 |
+
},
|
127 |
+
"separator": {
|
128 |
+
"background-color": "rgba(255, 255, 255, 0.1)"
|
129 |
+
}
|
130 |
}
|
131 |
)
|
132 |
|
utils/css/enhanced.css
ADDED
@@ -0,0 +1,400 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
enhanced.css - نظام تحليل المناقصات والعقود
|
3 |
+
تحسينات متقدمة وتأثيرات خاصة للواجهة
|
4 |
+
*/
|
5 |
+
|
6 |
+
/* ===== تأثيرات حركية ===== */
|
7 |
+
|
8 |
+
/* تأثير التلاشي عند إضافة أي عنصر */
|
9 |
+
@keyframes fadeIn {
|
10 |
+
from {
|
11 |
+
opacity: 0;
|
12 |
+
transform: translateY(10px);
|
13 |
+
}
|
14 |
+
to {
|
15 |
+
opacity: 1;
|
16 |
+
transform: translateY(0);
|
17 |
+
}
|
18 |
+
}
|
19 |
+
|
20 |
+
/* تأثير النبض للتنبيهات */
|
21 |
+
@keyframes pulse {
|
22 |
+
0% {
|
23 |
+
box-shadow: 0 0 0 0 rgba(255, 82, 82, 0.4);
|
24 |
+
}
|
25 |
+
70% {
|
26 |
+
box-shadow: 0 0 0 10px rgba(255, 82, 82, 0);
|
27 |
+
}
|
28 |
+
100% {
|
29 |
+
box-shadow: 0 0 0 0 rgba(255, 82, 82, 0);
|
30 |
+
}
|
31 |
+
}
|
32 |
+
|
33 |
+
/* تأثير الظهور التدريجي */
|
34 |
+
@keyframes slideIn {
|
35 |
+
from {
|
36 |
+
transform: translateX(30px);
|
37 |
+
opacity: 0;
|
38 |
+
}
|
39 |
+
to {
|
40 |
+
transform: translateX(0);
|
41 |
+
opacity: 1;
|
42 |
+
}
|
43 |
+
}
|
44 |
+
|
45 |
+
/* ===== تطبيق التأثيرات ===== */
|
46 |
+
|
47 |
+
/* تطبيق تأثير التلاشي على البطاقات الرئيسية */
|
48 |
+
.card,
|
49 |
+
.section-card,
|
50 |
+
.dashboard-card {
|
51 |
+
animation: fadeIn 0.5s ease-out;
|
52 |
+
}
|
53 |
+
|
54 |
+
/* تطبيق تأثير النبض على التنبيهات المهمة */
|
55 |
+
.alert-critical {
|
56 |
+
animation: pulse 2s infinite;
|
57 |
+
}
|
58 |
+
|
59 |
+
/* تطبيق تأثير الظهور التدريجي على القوائم */
|
60 |
+
.menu-item {
|
61 |
+
animation: slideIn 0.3s ease-out;
|
62 |
+
}
|
63 |
+
|
64 |
+
/* ===== تحسينات متقدمة للمكونات ===== */
|
65 |
+
|
66 |
+
/* تحسين تفاعلية البطاقات */
|
67 |
+
.card {
|
68 |
+
transition: all 0.3s ease;
|
69 |
+
border-right: 4px solid transparent;
|
70 |
+
}
|
71 |
+
|
72 |
+
.card:hover {
|
73 |
+
transform: translateY(-5px);
|
74 |
+
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.08);
|
75 |
+
border-right: 4px solid var(--primary-color);
|
76 |
+
}
|
77 |
+
|
78 |
+
/* تحسينات العناوين مع تأثيرات التدرجات */
|
79 |
+
.special-heading {
|
80 |
+
font-size: 2rem;
|
81 |
+
font-weight: 700;
|
82 |
+
background: linear-gradient(120deg, var(--primary-color), var(--accent-color));
|
83 |
+
-webkit-background-clip: text;
|
84 |
+
-webkit-text-fill-color: transparent;
|
85 |
+
position: relative;
|
86 |
+
display: inline-block;
|
87 |
+
margin-bottom: 1.5rem;
|
88 |
+
}
|
89 |
+
|
90 |
+
.special-heading::after {
|
91 |
+
content: '';
|
92 |
+
position: absolute;
|
93 |
+
bottom: -10px;
|
94 |
+
left: 0;
|
95 |
+
width: 60px;
|
96 |
+
height: 4px;
|
97 |
+
background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
|
98 |
+
border-radius: 2px;
|
99 |
+
}
|
100 |
+
|
101 |
+
/* قسم مع خلفية تدرج */
|
102 |
+
.gradient-section {
|
103 |
+
background: linear-gradient(120deg, rgba(11, 110, 79, 0.05), rgba(87, 84, 255, 0.05));
|
104 |
+
border-radius: var(--border-radius-lg);
|
105 |
+
padding: var(--spacing-lg);
|
106 |
+
margin-bottom: var(--spacing-lg);
|
107 |
+
}
|
108 |
+
|
109 |
+
/* أزرار بتأثيرات متقدمة */
|
110 |
+
.enhanced-button {
|
111 |
+
border: none;
|
112 |
+
padding: 10px 20px;
|
113 |
+
border-radius: var(--border-radius-md);
|
114 |
+
font-weight: bold;
|
115 |
+
transition: all 0.3s ease;
|
116 |
+
position: relative;
|
117 |
+
overflow: hidden;
|
118 |
+
z-index: 1;
|
119 |
+
color: var(--text-light);
|
120 |
+
background-color: var(--primary-color);
|
121 |
+
}
|
122 |
+
|
123 |
+
.enhanced-button::before {
|
124 |
+
content: '';
|
125 |
+
position: absolute;
|
126 |
+
top: 0;
|
127 |
+
left: 0;
|
128 |
+
width: 0;
|
129 |
+
height: 100%;
|
130 |
+
background-color: rgba(255, 255, 255, 0.1);
|
131 |
+
transition: width 0.3s ease;
|
132 |
+
z-index: -1;
|
133 |
+
}
|
134 |
+
|
135 |
+
.enhanced-button:hover::before {
|
136 |
+
width: 100%;
|
137 |
+
}
|
138 |
+
|
139 |
+
.enhanced-button:hover {
|
140 |
+
transform: translateY(-2px);
|
141 |
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
142 |
+
}
|
143 |
+
|
144 |
+
/* ===== مؤشرات وأشرطة التقدم المتقدمة ===== */
|
145 |
+
|
146 |
+
/* مؤشر تقدم دائري */
|
147 |
+
.circle-progress {
|
148 |
+
position: relative;
|
149 |
+
width: 100px;
|
150 |
+
height: 100px;
|
151 |
+
border-radius: 50%;
|
152 |
+
background: conic-gradient(var(--primary-color) 0%, var(--primary-light) var(--progress, 75%), #f0f0f0 var(--progress, 75%), #f0f0f0 100%);
|
153 |
+
display: flex;
|
154 |
+
align-items: center;
|
155 |
+
justify-content: center;
|
156 |
+
}
|
157 |
+
|
158 |
+
.circle-progress::before {
|
159 |
+
content: '';
|
160 |
+
position: absolute;
|
161 |
+
width: 70px;
|
162 |
+
height: 70px;
|
163 |
+
border-radius: 50%;
|
164 |
+
background-color: white;
|
165 |
+
}
|
166 |
+
|
167 |
+
.circle-progress-value {
|
168 |
+
position: relative;
|
169 |
+
z-index: 10;
|
170 |
+
font-weight: bold;
|
171 |
+
font-size: 1.2rem;
|
172 |
+
color: var(--primary-color);
|
173 |
+
}
|
174 |
+
|
175 |
+
/* مؤشر شريطي بتأثيرات */
|
176 |
+
.enhanced-progress {
|
177 |
+
height: 10px;
|
178 |
+
background-color: #f0f0f0;
|
179 |
+
border-radius: 5px;
|
180 |
+
overflow: hidden;
|
181 |
+
margin: 10px 0;
|
182 |
+
}
|
183 |
+
|
184 |
+
.enhanced-progress-bar {
|
185 |
+
height: 100%;
|
186 |
+
background: linear-gradient(90deg, var(--primary-color), var(--primary-light));
|
187 |
+
border-radius: 5px;
|
188 |
+
transition: width 0.5s ease;
|
189 |
+
position: relative;
|
190 |
+
overflow: hidden;
|
191 |
+
}
|
192 |
+
|
193 |
+
.enhanced-progress-bar::after {
|
194 |
+
content: '';
|
195 |
+
position: absolute;
|
196 |
+
top: 0;
|
197 |
+
left: 0;
|
198 |
+
right: 0;
|
199 |
+
bottom: 0;
|
200 |
+
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
201 |
+
animation: shimmer 1.5s infinite;
|
202 |
+
}
|
203 |
+
|
204 |
+
@keyframes shimmer {
|
205 |
+
0% {
|
206 |
+
transform: translateX(-100%);
|
207 |
+
}
|
208 |
+
100% {
|
209 |
+
transform: translateX(100%);
|
210 |
+
}
|
211 |
+
}
|
212 |
+
|
213 |
+
/* ===== بطاقات المؤشرات الإحصائية المطورة ===== */
|
214 |
+
|
215 |
+
.stat-card {
|
216 |
+
border-radius: var(--border-radius-md);
|
217 |
+
background-color: white;
|
218 |
+
padding: 1.5rem;
|
219 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
220 |
+
display: flex;
|
221 |
+
align-items: center;
|
222 |
+
transition: all 0.3s ease;
|
223 |
+
position: relative;
|
224 |
+
overflow: hidden;
|
225 |
+
}
|
226 |
+
|
227 |
+
.stat-card:hover {
|
228 |
+
transform: translateY(-5px);
|
229 |
+
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
230 |
+
}
|
231 |
+
|
232 |
+
.stat-card::before {
|
233 |
+
content: '';
|
234 |
+
position: absolute;
|
235 |
+
top: 0;
|
236 |
+
left: 0;
|
237 |
+
width: 6px;
|
238 |
+
height: 100%;
|
239 |
+
background-color: var(--primary-color);
|
240 |
+
}
|
241 |
+
|
242 |
+
.stat-card-icon {
|
243 |
+
width: 50px;
|
244 |
+
height: 50px;
|
245 |
+
display: flex;
|
246 |
+
align-items: center;
|
247 |
+
justify-content: center;
|
248 |
+
border-radius: 12px;
|
249 |
+
background-color: rgba(11, 110, 79, 0.1);
|
250 |
+
color: var(--primary-color);
|
251 |
+
margin-right: 1rem;
|
252 |
+
}
|
253 |
+
|
254 |
+
.stat-card-content {
|
255 |
+
flex-grow: 1;
|
256 |
+
}
|
257 |
+
|
258 |
+
.stat-card-value {
|
259 |
+
font-size: 2rem;
|
260 |
+
font-weight: bold;
|
261 |
+
color: var(--text-primary);
|
262 |
+
line-height: 1;
|
263 |
+
margin-bottom: 0.3rem;
|
264 |
+
}
|
265 |
+
|
266 |
+
.stat-card-label {
|
267 |
+
color: var(--text-muted);
|
268 |
+
font-size: 0.9rem;
|
269 |
+
}
|
270 |
+
|
271 |
+
.stat-card-trend {
|
272 |
+
display: flex;
|
273 |
+
align-items: center;
|
274 |
+
font-size: 0.8rem;
|
275 |
+
margin-top: 0.5rem;
|
276 |
+
}
|
277 |
+
|
278 |
+
.stat-card-trend.up {
|
279 |
+
color: var(--success-color);
|
280 |
+
}
|
281 |
+
|
282 |
+
.stat-card-trend.down {
|
283 |
+
color: var(--error-color);
|
284 |
+
}
|
285 |
+
|
286 |
+
/* ===== أشكال الخلفيات المحسنة ===== */
|
287 |
+
|
288 |
+
.dots-background {
|
289 |
+
background-color: white;
|
290 |
+
background-image: radial-gradient(rgba(11, 110, 79, 0.1) 1px, transparent 1px);
|
291 |
+
background-size: 20px 20px;
|
292 |
+
background-position: 0 0;
|
293 |
+
}
|
294 |
+
|
295 |
+
.geometric-pattern {
|
296 |
+
position: relative;
|
297 |
+
overflow: hidden;
|
298 |
+
z-index: 1;
|
299 |
+
}
|
300 |
+
|
301 |
+
.geometric-pattern::before {
|
302 |
+
content: "";
|
303 |
+
position: absolute;
|
304 |
+
top: -50%;
|
305 |
+
left: -50%;
|
306 |
+
width: 200%;
|
307 |
+
height: 200%;
|
308 |
+
background: repeating-linear-gradient(
|
309 |
+
45deg,
|
310 |
+
rgba(11, 110, 79, 0.03),
|
311 |
+
rgba(11, 110, 79, 0.03) 10px,
|
312 |
+
rgba(11, 110, 79, 0.05) 10px,
|
313 |
+
rgba(11, 110, 79, 0.05) 20px
|
314 |
+
);
|
315 |
+
transform: rotate(-45deg);
|
316 |
+
z-index: -1;
|
317 |
+
}
|
318 |
+
|
319 |
+
/* ===== تأثيرات تكميلية ===== */
|
320 |
+
|
321 |
+
/* تأثير الومضة على الأزرار عند النقر */
|
322 |
+
.button-flash {
|
323 |
+
position: relative;
|
324 |
+
overflow: hidden;
|
325 |
+
}
|
326 |
+
|
327 |
+
.button-flash::after {
|
328 |
+
content: '';
|
329 |
+
position: absolute;
|
330 |
+
top: 50%;
|
331 |
+
left: 50%;
|
332 |
+
width: 5px;
|
333 |
+
height: 5px;
|
334 |
+
background: rgba(255, 255, 255, 0.5);
|
335 |
+
opacity: 0;
|
336 |
+
border-radius: 100%;
|
337 |
+
transform: scale(1, 1) translate(-50%, -50%);
|
338 |
+
transform-origin: 50% 50%;
|
339 |
+
}
|
340 |
+
|
341 |
+
.button-flash:active::after {
|
342 |
+
animation: flash 0.6s ease-out;
|
343 |
+
}
|
344 |
+
|
345 |
+
@keyframes flash {
|
346 |
+
0% {
|
347 |
+
opacity: 1;
|
348 |
+
transform: scale(0, 0) translate(-50%, -50%);
|
349 |
+
}
|
350 |
+
100% {
|
351 |
+
opacity: 0;
|
352 |
+
transform: scale(50, 50) translate(-50%, -50%);
|
353 |
+
}
|
354 |
+
}
|
355 |
+
|
356 |
+
/* ===== تحسينات المسافات والهوامش لسهولة القراءة ===== */
|
357 |
+
|
358 |
+
/* إضافة مسافات أكبر بين الفقرات */
|
359 |
+
p {
|
360 |
+
margin-bottom: 1.2rem;
|
361 |
+
line-height: 1.6;
|
362 |
+
}
|
363 |
+
|
364 |
+
/* تحسين النقاط والقوائم */
|
365 |
+
li {
|
366 |
+
margin-bottom: 0.5rem;
|
367 |
+
}
|
368 |
+
|
369 |
+
/* ===== تعديلات خاصة للهواتف المحمولة ===== */
|
370 |
+
|
371 |
+
@media (max-width: 768px) {
|
372 |
+
.dashboard-grid {
|
373 |
+
grid-template-columns: 1fr;
|
374 |
+
}
|
375 |
+
|
376 |
+
.stat-card {
|
377 |
+
padding: 1rem;
|
378 |
+
}
|
379 |
+
|
380 |
+
.stat-card-icon {
|
381 |
+
width: 40px;
|
382 |
+
height: 40px;
|
383 |
+
}
|
384 |
+
|
385 |
+
.stat-card-value {
|
386 |
+
font-size: 1.5rem;
|
387 |
+
}
|
388 |
+
|
389 |
+
h1 {
|
390 |
+
font-size: 1.8rem;
|
391 |
+
}
|
392 |
+
|
393 |
+
h2 {
|
394 |
+
font-size: 1.5rem;
|
395 |
+
}
|
396 |
+
|
397 |
+
.indicator-value {
|
398 |
+
font-size: 1.5rem;
|
399 |
+
}
|
400 |
+
}
|
utils/css/main.css
ADDED
@@ -0,0 +1,405 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
main.css - نظام تحليل المناقصات والعقود
|
3 |
+
الأنماط الرئيسية للتطبيق
|
4 |
+
*/
|
5 |
+
|
6 |
+
/* ===== المتغيرات العامة ===== */
|
7 |
+
:root {
|
8 |
+
/* الألوان الأساسية */
|
9 |
+
--primary-color: #0B6E4F;
|
10 |
+
--primary-light: #08A45C;
|
11 |
+
--primary-dark: #074D37;
|
12 |
+
--secondary-color: #FFB100;
|
13 |
+
--secondary-light: #FFCA4D;
|
14 |
+
--secondary-dark: #D99000;
|
15 |
+
--accent-color: #5754FF;
|
16 |
+
--accent-light: #6F6CFF;
|
17 |
+
--accent-dark: #4240DD;
|
18 |
+
|
19 |
+
/* ألوان محايدة */
|
20 |
+
--background-color: #F8F9FA;
|
21 |
+
--surface-color: #FFFFFF;
|
22 |
+
--card-color: #FAFAFA;
|
23 |
+
--border-color: #E0E0E0;
|
24 |
+
|
25 |
+
/* ألوان النص */
|
26 |
+
--text-primary: #222222;
|
27 |
+
--text-secondary: #555555;
|
28 |
+
--text-muted: #888888;
|
29 |
+
--text-light: #FFFFFF;
|
30 |
+
|
31 |
+
/* ألوان مؤشرات الحالة */
|
32 |
+
--success-color: #43A047;
|
33 |
+
--warning-color: #FB8C00;
|
34 |
+
--error-color: #E53935;
|
35 |
+
--info-color: #1E88E5;
|
36 |
+
|
37 |
+
/* القياسات */
|
38 |
+
--border-radius-sm: 4px;
|
39 |
+
--border-radius-md: 8px;
|
40 |
+
--border-radius-lg: 16px;
|
41 |
+
--spacing-xs: 4px;
|
42 |
+
--spacing-sm: 8px;
|
43 |
+
--spacing-md: 16px;
|
44 |
+
--spacing-lg: 24px;
|
45 |
+
--spacing-xl: 32px;
|
46 |
+
|
47 |
+
/* الظلال */
|
48 |
+
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.05);
|
49 |
+
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.1);
|
50 |
+
--shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.1);
|
51 |
+
|
52 |
+
/* الخطوط */
|
53 |
+
--font-family: 'Cairo', 'Tajawal', 'Readex Pro', sans-serif;
|
54 |
+
}
|
55 |
+
|
56 |
+
/* ===== إعدادات عامة ===== */
|
57 |
+
html, body {
|
58 |
+
font-family: var(--font-family);
|
59 |
+
color: var(--text-primary);
|
60 |
+
background-color: var(--background-color);
|
61 |
+
}
|
62 |
+
|
63 |
+
/* ===== العناوين ===== */
|
64 |
+
h1, h2, h3, h4, h5, h6 {
|
65 |
+
color: var(--primary-color);
|
66 |
+
font-weight: 700;
|
67 |
+
margin-bottom: var(--spacing-md);
|
68 |
+
}
|
69 |
+
|
70 |
+
h1 {
|
71 |
+
font-size: 2.2rem;
|
72 |
+
background: linear-gradient(90deg, var(--primary-color), var(--primary-light));
|
73 |
+
-webkit-background-clip: text;
|
74 |
+
-webkit-text-fill-color: transparent;
|
75 |
+
display: inline-block;
|
76 |
+
margin-bottom: var(--spacing-lg);
|
77 |
+
}
|
78 |
+
|
79 |
+
h2 {
|
80 |
+
font-size: 1.8rem;
|
81 |
+
position: relative;
|
82 |
+
padding-bottom: var(--spacing-sm);
|
83 |
+
}
|
84 |
+
|
85 |
+
h2::after {
|
86 |
+
content: '';
|
87 |
+
position: absolute;
|
88 |
+
bottom: 0;
|
89 |
+
right: 0;
|
90 |
+
width: 60px;
|
91 |
+
height: 3px;
|
92 |
+
background: var(--secondary-color);
|
93 |
+
border-radius: var(--border-radius-sm);
|
94 |
+
}
|
95 |
+
|
96 |
+
h3 {
|
97 |
+
font-size: 1.5rem;
|
98 |
+
color: var(--primary-dark);
|
99 |
+
}
|
100 |
+
|
101 |
+
h4 {
|
102 |
+
font-size: 1.2rem;
|
103 |
+
color: var(--text-primary);
|
104 |
+
}
|
105 |
+
|
106 |
+
/* ===== عنوان التطبيق ===== */
|
107 |
+
.app-title {
|
108 |
+
font-size: 2.2rem;
|
109 |
+
font-weight: bold;
|
110 |
+
text-align: center;
|
111 |
+
color: var(--primary-color);
|
112 |
+
margin-bottom: var(--spacing-lg);
|
113 |
+
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
|
114 |
+
background: linear-gradient(90deg, var(--primary-color), var(--primary-light));
|
115 |
+
-webkit-background-clip: text;
|
116 |
+
-webkit-text-fill-color: transparent;
|
117 |
+
}
|
118 |
+
|
119 |
+
/* ===== الأقسام والبطاقات ===== */
|
120 |
+
.section-card {
|
121 |
+
background-color: var(--surface-color);
|
122 |
+
border-radius: var(--border-radius-lg);
|
123 |
+
padding: var(--spacing-lg);
|
124 |
+
box-shadow: var(--shadow-md);
|
125 |
+
margin-bottom: var(--spacing-lg);
|
126 |
+
border-right: 4px solid var(--primary-color);
|
127 |
+
}
|
128 |
+
|
129 |
+
.card {
|
130 |
+
background-color: var(--surface-color);
|
131 |
+
border-radius: var(--border-radius-md);
|
132 |
+
padding: var(--spacing-md);
|
133 |
+
box-shadow: var(--shadow-sm);
|
134 |
+
margin-bottom: var(--spacing-md);
|
135 |
+
border-right: 3px solid var(--primary-color);
|
136 |
+
transition: all 0.3s ease;
|
137 |
+
}
|
138 |
+
|
139 |
+
.card:hover {
|
140 |
+
box-shadow: var(--shadow-md);
|
141 |
+
transform: translateY(-2px);
|
142 |
+
}
|
143 |
+
|
144 |
+
.card-title {
|
145 |
+
font-weight: bold;
|
146 |
+
color: var(--primary-color);
|
147 |
+
margin-bottom: var(--spacing-sm);
|
148 |
+
font-size: 1.2rem;
|
149 |
+
}
|
150 |
+
|
151 |
+
.card-metrics {
|
152 |
+
display: flex;
|
153 |
+
justify-content: space-between;
|
154 |
+
margin-top: var(--spacing-sm);
|
155 |
+
}
|
156 |
+
|
157 |
+
.card-metric {
|
158 |
+
text-align: center;
|
159 |
+
}
|
160 |
+
|
161 |
+
.card-metric-value {
|
162 |
+
font-weight: bold;
|
163 |
+
font-size: 1.5rem;
|
164 |
+
}
|
165 |
+
|
166 |
+
.card-metric-label {
|
167 |
+
font-size: 0.8rem;
|
168 |
+
color: var(--text-muted);
|
169 |
+
}
|
170 |
+
|
171 |
+
/* ===== المؤشرات ===== */
|
172 |
+
.indicator {
|
173 |
+
padding: var(--spacing-md);
|
174 |
+
border-radius: var(--border-radius-md);
|
175 |
+
background-color: var(--surface-color);
|
176 |
+
text-align: center;
|
177 |
+
box-shadow: var(--shadow-sm);
|
178 |
+
border-top: 3px solid var(--primary-color);
|
179 |
+
height: 100%;
|
180 |
+
}
|
181 |
+
|
182 |
+
.indicator-value {
|
183 |
+
font-size: 2rem;
|
184 |
+
font-weight: bold;
|
185 |
+
margin-bottom: var(--spacing-xs);
|
186 |
+
}
|
187 |
+
|
188 |
+
.indicator-label {
|
189 |
+
font-size: 1rem;
|
190 |
+
color: var(--text-muted);
|
191 |
+
}
|
192 |
+
|
193 |
+
/* ===== الأزرار ===== */
|
194 |
+
.stButton > button {
|
195 |
+
border-radius: var(--border-radius-md);
|
196 |
+
background-color: var(--primary-color);
|
197 |
+
color: var(--text-light);
|
198 |
+
font-weight: bold;
|
199 |
+
border: none;
|
200 |
+
padding: 0.5rem 1rem;
|
201 |
+
transition: all 0.3s ease;
|
202 |
+
}
|
203 |
+
|
204 |
+
.stButton > button:hover {
|
205 |
+
background-color: var(--primary-dark);
|
206 |
+
box-shadow: var(--shadow-sm);
|
207 |
+
}
|
208 |
+
|
209 |
+
/* زر ثانوي */
|
210 |
+
.stButton.secondary > button {
|
211 |
+
background-color: var(--secondary-color);
|
212 |
+
color: var(--text-primary);
|
213 |
+
}
|
214 |
+
|
215 |
+
.stButton.secondary > button:hover {
|
216 |
+
background-color: var(--secondary-dark);
|
217 |
+
}
|
218 |
+
|
219 |
+
/* زر محايد */
|
220 |
+
.stButton.neutral > button {
|
221 |
+
background-color: var(--text-muted);
|
222 |
+
color: var(--text-light);
|
223 |
+
}
|
224 |
+
|
225 |
+
.stButton.neutral > button:hover {
|
226 |
+
background-color: var(--text-secondary);
|
227 |
+
}
|
228 |
+
|
229 |
+
/* زر خطر */
|
230 |
+
.stButton.danger > button {
|
231 |
+
background-color: var(--error-color);
|
232 |
+
}
|
233 |
+
|
234 |
+
.stButton.danger > button:hover {
|
235 |
+
background-color: #C62828;
|
236 |
+
}
|
237 |
+
|
238 |
+
/* ===== النماذج ===== */
|
239 |
+
.stTextInput > div > div > input,
|
240 |
+
.stTextArea > div > div > textarea {
|
241 |
+
border-radius: var(--border-radius-md);
|
242 |
+
border: 1px solid var(--border-color);
|
243 |
+
padding: var(--spacing-sm);
|
244 |
+
transition: all 0.3s ease;
|
245 |
+
}
|
246 |
+
|
247 |
+
.stTextInput > div > div > input:focus,
|
248 |
+
.stTextArea > div > div > textarea:focus {
|
249 |
+
border-color: var(--primary-color);
|
250 |
+
box-shadow: 0 0 0 2px rgba(11, 110, 79, 0.2);
|
251 |
+
}
|
252 |
+
|
253 |
+
.stSelectbox > div > div > div {
|
254 |
+
border-radius: var(--border-radius-md);
|
255 |
+
border: 1px solid var(--border-color);
|
256 |
+
}
|
257 |
+
|
258 |
+
/* ===== الفواصل ===== */
|
259 |
+
hr {
|
260 |
+
border: none;
|
261 |
+
height: 1px;
|
262 |
+
background-color: var(--border-color);
|
263 |
+
margin: var(--spacing-lg) 0;
|
264 |
+
}
|
265 |
+
|
266 |
+
/* ===== الرسوم البيانية ===== */
|
267 |
+
.stPlot {
|
268 |
+
background-color: var(--surface-color);
|
269 |
+
border-radius: var(--border-radius-md);
|
270 |
+
padding: var(--spacing-md);
|
271 |
+
box-shadow: var(--shadow-sm);
|
272 |
+
}
|
273 |
+
|
274 |
+
/* ===== التنبيهات ===== */
|
275 |
+
.stAlert {
|
276 |
+
border-radius: var(--border-radius-md);
|
277 |
+
box-shadow: var(--shadow-sm);
|
278 |
+
}
|
279 |
+
|
280 |
+
/* ===== الصور ===== */
|
281 |
+
.stImage > img {
|
282 |
+
border-radius: var(--border-radius-md);
|
283 |
+
box-shadow: var(--shadow-sm);
|
284 |
+
}
|
285 |
+
|
286 |
+
/* ===== تعديلات خاصة بالواجهة ===== */
|
287 |
+
.main-content {
|
288 |
+
padding: var(--spacing-md);
|
289 |
+
}
|
290 |
+
|
291 |
+
/* ===== أنماط القائمة ===== */
|
292 |
+
.menu-container {
|
293 |
+
background-color: var(--surface-color);
|
294 |
+
border-radius: var(--border-radius-md);
|
295 |
+
box-shadow: var(--shadow-sm);
|
296 |
+
padding: var(--spacing-sm);
|
297 |
+
margin-bottom: var(--spacing-md);
|
298 |
+
}
|
299 |
+
|
300 |
+
.menu-item {
|
301 |
+
padding: var(--spacing-sm);
|
302 |
+
border-radius: var(--border-radius-sm);
|
303 |
+
cursor: pointer;
|
304 |
+
transition: all 0.3s ease;
|
305 |
+
}
|
306 |
+
|
307 |
+
.menu-item:hover {
|
308 |
+
background-color: rgba(11, 110, 79, 0.1);
|
309 |
+
}
|
310 |
+
|
311 |
+
.menu-item.active {
|
312 |
+
background-color: var(--primary-color);
|
313 |
+
color: var(--text-light);
|
314 |
+
}
|
315 |
+
|
316 |
+
/* ===== لوحات المعلومات ===== */
|
317 |
+
.dashboard-grid {
|
318 |
+
display: grid;
|
319 |
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
320 |
+
gap: var(--spacing-md);
|
321 |
+
margin-bottom: var(--spacing-lg);
|
322 |
+
}
|
323 |
+
|
324 |
+
.dashboard-card {
|
325 |
+
background-color: var(--surface-color);
|
326 |
+
border-radius: var(--border-radius-md);
|
327 |
+
padding: var(--spacing-md);
|
328 |
+
box-shadow: var(--shadow-sm);
|
329 |
+
display: flex;
|
330 |
+
flex-direction: column;
|
331 |
+
height: 100%;
|
332 |
+
}
|
333 |
+
|
334 |
+
.dashboard-card-header {
|
335 |
+
display: flex;
|
336 |
+
justify-content: space-between;
|
337 |
+
align-items: center;
|
338 |
+
margin-bottom: var(--spacing-sm);
|
339 |
+
padding-bottom: var(--spacing-sm);
|
340 |
+
border-bottom: 1px solid var(--border-color);
|
341 |
+
}
|
342 |
+
|
343 |
+
.dashboard-card-title {
|
344 |
+
font-weight: bold;
|
345 |
+
color: var(--primary-color);
|
346 |
+
}
|
347 |
+
|
348 |
+
.dashboard-card-icon {
|
349 |
+
color: var(--accent-color);
|
350 |
+
background-color: rgba(87, 84, 255, 0.1);
|
351 |
+
width: 40px;
|
352 |
+
height: 40px;
|
353 |
+
display: flex;
|
354 |
+
align-items: center;
|
355 |
+
justify-content: center;
|
356 |
+
border-radius: 50%;
|
357 |
+
}
|
358 |
+
|
359 |
+
.dashboard-card-content {
|
360 |
+
flex-grow: 1;
|
361 |
+
}
|
362 |
+
|
363 |
+
.dashboard-card-footer {
|
364 |
+
margin-top: var(--spacing-sm);
|
365 |
+
padding-top: var(--spacing-sm);
|
366 |
+
border-top: 1px solid var(--border-color);
|
367 |
+
display: flex;
|
368 |
+
justify-content: space-between;
|
369 |
+
align-items: center;
|
370 |
+
}
|
371 |
+
|
372 |
+
/* ===== المحتوى المشروط ===== */
|
373 |
+
.conditional-section {
|
374 |
+
transition: all 0.3s ease;
|
375 |
+
animation: fadeIn 0.3s ease-in-out;
|
376 |
+
}
|
377 |
+
|
378 |
+
@keyframes fadeIn {
|
379 |
+
from {
|
380 |
+
opacity: 0;
|
381 |
+
transform: translateY(10px);
|
382 |
+
}
|
383 |
+
to {
|
384 |
+
opacity: 1;
|
385 |
+
transform: translateY(0);
|
386 |
+
}
|
387 |
+
}
|
388 |
+
|
389 |
+
/* ===== نمط تذييل الصفحة ===== */
|
390 |
+
.footer {
|
391 |
+
text-align: center;
|
392 |
+
padding: var(--spacing-lg) 0;
|
393 |
+
color: var(--text-muted);
|
394 |
+
font-size: 0.9rem;
|
395 |
+
margin-top: var(--spacing-xl);
|
396 |
+
}
|
397 |
+
|
398 |
+
.footer a {
|
399 |
+
color: var(--primary-color);
|
400 |
+
text-decoration: none;
|
401 |
+
}
|
402 |
+
|
403 |
+
.footer a:hover {
|
404 |
+
text-decoration: underline;
|
405 |
+
}
|
utils/css/rtl.css
ADDED
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
rtl.css - نظام تحليل المناقصات والعقود
|
3 |
+
تخصيصات الواجهة للغة العربية ودعم RTL
|
4 |
+
*/
|
5 |
+
|
6 |
+
/* ===== تعديلات عامة لتنسيق RTL ===== */
|
7 |
+
|
8 |
+
/* توجيه النص العام والمكونات */
|
9 |
+
.css-18e3th9,
|
10 |
+
.css-1d391kg,
|
11 |
+
.stMarkdown,
|
12 |
+
.stTextArea,
|
13 |
+
.stButton,
|
14 |
+
.stTextInput,
|
15 |
+
.stSelectbox,
|
16 |
+
.stRadio,
|
17 |
+
.stCheckbox,
|
18 |
+
.stTabs,
|
19 |
+
.stWidgetLabel,
|
20 |
+
.stPlotlyChart,
|
21 |
+
.stDataFrame,
|
22 |
+
.stTable {
|
23 |
+
direction: rtl;
|
24 |
+
text-align: right;
|
25 |
+
}
|
26 |
+
|
27 |
+
/* توجيه صحيح للرسوم البيانية */
|
28 |
+
.plotly-graph-div,
|
29 |
+
.stPlot {
|
30 |
+
direction: ltr;
|
31 |
+
}
|
32 |
+
|
33 |
+
/* تعديل على عناصر القوائم */
|
34 |
+
.stSelectbox > div > div > div {
|
35 |
+
text-align: right;
|
36 |
+
}
|
37 |
+
|
38 |
+
/* إصلاح اتجاه المدخلات الرقمية */
|
39 |
+
input[type="number"] {
|
40 |
+
direction: ltr;
|
41 |
+
text-align: right;
|
42 |
+
}
|
43 |
+
|
44 |
+
/* تعديل نص الأزرار للتوجيه الصحيح */
|
45 |
+
.stButton > button {
|
46 |
+
direction: rtl;
|
47 |
+
display: flex;
|
48 |
+
align-items: center;
|
49 |
+
justify-content: center;
|
50 |
+
}
|
51 |
+
|
52 |
+
/* تعديل الشاشة الرئيسية */
|
53 |
+
.main .block-container {
|
54 |
+
direction: rtl;
|
55 |
+
text-align: right;
|
56 |
+
}
|
57 |
+
|
58 |
+
/* ===== تعديلات أيقونات وعلامات خاصة ===== */
|
59 |
+
|
60 |
+
/* عكس اتجاه أيقونات السهم والأيقونات الموجهة الأخرى */
|
61 |
+
.fa-arrow-left,
|
62 |
+
.fa-chevron-left {
|
63 |
+
transform: rotate(180deg);
|
64 |
+
}
|
65 |
+
|
66 |
+
.fa-arrow-right,
|
67 |
+
.fa-chevron-right {
|
68 |
+
transform: rotate(180deg);
|
69 |
+
}
|
70 |
+
|
71 |
+
/* تعديل اتجاه علامات التبويب */
|
72 |
+
.stTabs [role="tablist"] {
|
73 |
+
flex-direction: row-reverse;
|
74 |
+
}
|
75 |
+
|
76 |
+
/* تعديل على مؤشرات التحميل */
|
77 |
+
.stProgress > div > div > div {
|
78 |
+
flex-direction: row-reverse;
|
79 |
+
}
|
80 |
+
|
81 |
+
/* ===== تعديلات خاصة للجداول والبيانات ===== */
|
82 |
+
|
83 |
+
/* إصلاح اتجاه الجداول */
|
84 |
+
.stTable {
|
85 |
+
direction: rtl;
|
86 |
+
}
|
87 |
+
|
88 |
+
.stTable th {
|
89 |
+
text-align: right;
|
90 |
+
}
|
91 |
+
|
92 |
+
/* رؤوس الجداول */
|
93 |
+
.stTable thead {
|
94 |
+
text-align: right;
|
95 |
+
}
|
96 |
+
|
97 |
+
/* صفوف البيانات */
|
98 |
+
.stTable tbody {
|
99 |
+
text-align: right;
|
100 |
+
}
|
101 |
+
|
102 |
+
/* تعديل على مكون التاريخ */
|
103 |
+
.stDateInput > div {
|
104 |
+
flex-direction: row-reverse;
|
105 |
+
}
|
106 |
+
|
107 |
+
/* ===== تحسينات إضافية ===== */
|
108 |
+
|
109 |
+
/* تعديل مكونات التفاعل */
|
110 |
+
.stWidgetLabel p {
|
111 |
+
text-align: right;
|
112 |
+
}
|
113 |
+
|
114 |
+
/* تعديل على الهوامش - تبديل الهوامش اليمنى واليسرى */
|
115 |
+
.stButton,
|
116 |
+
.stTextInput,
|
117 |
+
.stNumberInput,
|
118 |
+
.stSelectbox,
|
119 |
+
.stTextArea {
|
120 |
+
margin-right: 0;
|
121 |
+
margin-left: auto;
|
122 |
+
}
|
123 |
+
|
124 |
+
/* تعديل على الحدود - تبديل الحدود اليمنى واليسرى */
|
125 |
+
.stMarkdown blockquote {
|
126 |
+
border-right: 4px solid var(--primary-color);
|
127 |
+
border-left: none;
|
128 |
+
padding-right: 20px;
|
129 |
+
padding-left: 0;
|
130 |
+
}
|
131 |
+
|
132 |
+
/* إضافة دعم أفضل للخطوط العربية */
|
133 |
+
body {
|
134 |
+
font-family: 'Cairo', 'Tajawal', 'Readex Pro', -apple-system, BlinkMacSystemFont, sans-serif;
|
135 |
+
}
|
136 |
+
|
137 |
+
/* تعديل صناديق الإدخال */
|
138 |
+
textarea, input {
|
139 |
+
font-family: 'Cairo', 'Tajawal', 'Readex Pro', -apple-system, BlinkMacSystemFont, sans-serif;
|
140 |
+
}
|
141 |
+
|
142 |
+
/* تعديل القوائم ذات التعداد النقطي للاتجاه RTL */
|
143 |
+
ul, ol {
|
144 |
+
padding-right: 20px;
|
145 |
+
padding-left: 0;
|
146 |
+
}
|
147 |
+
|
148 |
+
/* ===== تصحيحات محددة للمكونات ===== */
|
149 |
+
|
150 |
+
/* تصحيح التنسيق للأعمدة */
|
151 |
+
div.row-widget.stRadio > div {
|
152 |
+
flex-direction: row-reverse;
|
153 |
+
}
|
154 |
+
|
155 |
+
/* تصحيح مربعات الاختيار */
|
156 |
+
.stCheckbox > div {
|
157 |
+
flex-direction: row-reverse;
|
158 |
+
}
|
159 |
+
|
160 |
+
/* تحسين مظهر وتجربة المستخدم العربية */
|
161 |
+
.stExpander > div > div > div {
|
162 |
+
text-align: right;
|
163 |
+
}
|
164 |
+
|
165 |
+
.stFileUploader > div {
|
166 |
+
direction: rtl;
|
167 |
+
text-align: right;
|
168 |
+
}
|
169 |
+
|
170 |
+
/* تصحيح ظهور القوائم المنسدلة لتلائم RTL */
|
171 |
+
.stSelectbox > div[data-baseweb="select"] > div {
|
172 |
+
direction: rtl;
|
173 |
+
}
|
174 |
+
|
175 |
+
/* تصحيح اتجاه النص في التنبيهات */
|
176 |
+
.stAlert > div {
|
177 |
+
flex-direction: row-reverse;
|
178 |
+
text-align: right;
|
179 |
+
}
|
180 |
+
|
181 |
+
/* تصحيح اتجاه صناديق النص القابلة للتحرير */
|
182 |
+
.css-1hynsf2 .stTextArea label,
|
183 |
+
.css-1hynsf2 .stTextInput label {
|
184 |
+
text-align: right;
|
185 |
+
}
|
186 |
+
|
187 |
+
/* تصحيح مربعات الاختيار المتعددة */
|
188 |
+
div.row-widget.stMultiselect > div {
|
189 |
+
direction: rtl;
|
190 |
+
}
|
191 |
+
|
192 |
+
/* تصحيح محاذاة محاذاة المحتوى وترتيب الأيقونات */
|
193 |
+
.stAlert > div > div:first-child {
|
194 |
+
margin-left: 10px;
|
195 |
+
margin-right: 0;
|
196 |
+
}
|
197 |
+
|
198 |
+
/* تصحيحات عامة للتأكد من عدم انعكاس أي شيء بشكل غير صحيح */
|
199 |
+
.stMarkdown img {
|
200 |
+
direction: ltr;
|
201 |
+
}
|
202 |
+
|
203 |
+
/* ===== تصحيحات لحالات خاصة ===== */
|
204 |
+
|
205 |
+
/* تصحيح اتجاه مكونات التاريخ والوقت */
|
206 |
+
.stDateInput, .stTimeInput {
|
207 |
+
direction: rtl;
|
208 |
+
}
|
209 |
+
|
210 |
+
/* تصحيح محدد اللون */
|
211 |
+
.stColorPicker > div {
|
212 |
+
direction: ltr;
|
213 |
+
}
|
214 |
+
|
215 |
+
/* تصحيح مكون التصفية بالنوع */
|
216 |
+
.stType > div {
|
217 |
+
direction: rtl;
|
218 |
+
}
|
219 |
+
|
220 |
+
/* تصحيح مكون الشريط الجان��ي */
|
221 |
+
.css-1v3fvcr {
|
222 |
+
direction: rtl;
|
223 |
+
}
|
utils/helpers/__init__.py
CHANGED
@@ -6,6 +6,8 @@
|
|
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,6 +25,33 @@ from .utils import (
|
|
23 |
extract_numbers_from_text
|
24 |
)
|
25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
__all__ = [
|
27 |
'create_directory_if_not_exists',
|
28 |
'get_data_folder',
|
@@ -36,5 +65,6 @@ __all__ = [
|
|
36 |
'styled_button',
|
37 |
'filter_dataframe',
|
38 |
'get_file_extension',
|
39 |
-
'extract_numbers_from_text'
|
|
|
40 |
]
|
|
|
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 |
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 |
'styled_button',
|
66 |
'filter_dataframe',
|
67 |
'get_file_extension',
|
68 |
+
'extract_numbers_from_text',
|
69 |
+
'get_connection'
|
70 |
]
|
utils/helpers/utils.py
CHANGED
@@ -199,23 +199,47 @@ def format_currency(amount, currency="ريال", decimal_places=2):
|
|
199 |
formatted = format_number(amount, decimal_places)
|
200 |
return f"{formatted} {currency}"
|
201 |
|
202 |
-
def styled_button(label,
|
203 |
-
"""
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
208 |
"""
|
209 |
-
|
210 |
if is_link:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
211 |
return st.markdown(button_html, unsafe_allow_html=True)
|
212 |
else:
|
213 |
-
|
214 |
-
f"{
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
219 |
|
220 |
def filter_dataframe(df, column, value):
|
221 |
"""ترشيح إطار البيانات"""
|
|
|
199 |
formatted = format_number(amount, decimal_places)
|
200 |
return f"{formatted} {currency}"
|
201 |
|
202 |
+
def styled_button(label, key=None, type="primary", on_click=None, args=None, full_width=False, icon=None, is_link=False, help=None):
|
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 |
"""ترشيح إطار البيانات"""
|