EGYADMIN commited on
Commit
4d05599
·
verified ·
1 Parent(s): 849c31b

Update styling/enhanced_ui.py

Browse files
Files changed (1) hide show
  1. styling/enhanced_ui.py +582 -582
styling/enhanced_ui.py CHANGED
@@ -1,582 +1,582 @@
1
- """
2
- محسن واجهة المستخدم - نظام تحليل المناقصات
3
- """
4
-
5
- import streamlit as st
6
- import pandas as pd
7
- import numpy as np
8
- import base64
9
- from pathlib import Path
10
- import os
11
-
12
- class UIEnhancer:
13
- """فئة لتحسين واجهة المستخدم وتوحيد التصميم عبر النظام"""
14
-
15
- # ألوان النظام
16
- COLORS = {
17
- 'primary': '#1E88E5', # أزرق
18
- 'secondary': '#5E35B1', # بنفسجي
19
- 'success': '#43A047', # أخضر
20
- 'warning': '#FB8C00', # برتقالي
21
- 'danger': '#E53935', # أحمر
22
- 'info': '#00ACC1', # سماوي
23
- 'light': '#F5F5F5', # رمادي فاتح
24
- 'dark': '#212121', # رمادي داكن
25
- 'accent': '#FF4081', # وردي
26
- 'background': '#FFFFFF', # أبيض
27
- 'text': '#212121', # أسود
28
- 'border': '#E0E0E0' # رمادي حدود
29
- }
30
-
31
- # أحجام الخطوط
32
- FONT_SIZES = {
33
- 'xs': '0.75rem',
34
- 'sm': '0.875rem',
35
- 'md': '1rem',
36
- 'lg': '1.125rem',
37
- 'xl': '1.25rem',
38
- '2xl': '1.5rem',
39
- '3xl': '1.875rem',
40
- '4xl': '2.25rem',
41
- '5xl': '3rem'
42
- }
43
-
44
- def __init__(self, page_title="نظام تحليل المناقصات", page_icon="📊"):
45
- """تهيئة محسن واجهة المستخدم"""
46
- self.page_title = page_title
47
- self.page_icon = page_icon
48
- self.theme_mode = "light" # الوضع الافتراضي هو الوضع الفاتح
49
-
50
- # تهيئة متغير السمة في حالة الجلسة إذا لم يكن موجوداً
51
- if 'theme' not in st.session_state:
52
- st.session_state.theme = 'light'
53
-
54
- def apply_global_styles(self):
55
- """تطبيق التنسيقات العامة على الصفحة"""
56
- # تعريف CSS العام
57
- css = f"""
58
- @import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700&display=swap');
59
-
60
- * {{
61
- font-family: 'Tajawal', sans-serif;
62
- direction: rtl;
63
- }}
64
-
65
- h1, h2, h3, h4, h5, h6 {{
66
- font-family: 'Tajawal', sans-serif;
67
- font-weight: 700;
68
- color: {self.COLORS['dark']};
69
- }}
70
-
71
- .module-title {{
72
- color: {self.COLORS['primary']};
73
- font-size: {self.FONT_SIZES['3xl']};
74
- margin-bottom: 1rem;
75
- border-bottom: 2px solid {self.COLORS['primary']};
76
- padding-bottom: 0.5rem;
77
- }}
78
-
79
- .stTabs [data-baseweb="tab-list"] {{
80
- gap: 2px;
81
- }}
82
-
83
- .stTabs [data-baseweb="tab"] {{
84
- height: 50px;
85
- white-space: pre-wrap;
86
- background-color: {self.COLORS['light']};
87
- border-radius: 4px 4px 0 0;
88
- gap: 1px;
89
- padding-top: 10px;
90
- padding-bottom: 10px;
91
- }}
92
-
93
- .stTabs [aria-selected="true"] {{
94
- background-color: {self.COLORS['primary']};
95
- color: white;
96
- }}
97
-
98
- div[data-testid="stSidebarNav"] li div a span {{
99
- direction: rtl;
100
- text-align: right;
101
- font-family: 'Tajawal', sans-serif;
102
- }}
103
-
104
- div[data-testid="stSidebarNav"] {{
105
- background-color: {self.COLORS['light']};
106
- }}
107
-
108
- div[data-testid="stSidebarNav"] li div {{
109
- margin-right: 0;
110
- margin-left: auto;
111
- }}
112
-
113
- div[data-testid="stSidebarNav"] li div a {{
114
- padding-right: 10px;
115
- padding-left: 0;
116
- }}
117
-
118
- div[data-testid="stSidebarNav"] li div a:hover {{
119
- background-color: {self.COLORS['primary'] + '20'};
120
- }}
121
-
122
- div[data-testid="stSidebarNav"] li div[aria-selected="true"] {{
123
- background-color: {self.COLORS['primary'] + '40'};
124
- }}
125
-
126
- div[data-testid="stSidebarNav"] li div[aria-selected="true"] a span {{
127
- color: {self.COLORS['primary']};
128
- font-weight: 500;
129
- }}
130
-
131
- .metric-card {{
132
- background-color: white;
133
- border-radius: 10px;
134
- padding: 20px;
135
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
136
- text-align: center;
137
- transition: transform 0.3s ease;
138
- }}
139
-
140
- .metric-card:hover {{
141
- transform: translateY(-5px);
142
- }}
143
-
144
- .metric-value {{
145
- font-size: 2.5rem;
146
- font-weight: 700;
147
- margin: 10px 0;
148
- }}
149
-
150
- .metric-label {{
151
- font-size: 1rem;
152
- color: #666;
153
- }}
154
-
155
- .metric-change {{
156
- font-size: 0.9rem;
157
- margin-top: 5px;
158
- }}
159
-
160
- .metric-change-positive {{
161
- color: {self.COLORS['success']};
162
- }}
163
-
164
- .metric-change-negative {{
165
- color: {self.COLORS['danger']};
166
- }}
167
-
168
- .custom-button {{
169
- background-color: {self.COLORS['primary']};
170
- color: white;
171
- border: none;
172
- border-radius: 5px;
173
- padding: 10px 20px;
174
- font-size: 1rem;
175
- cursor: pointer;
176
- transition: background-color 0.3s ease;
177
- }}
178
-
179
- .custom-button:hover {{
180
- background-color: {self.COLORS['secondary']};
181
- }}
182
-
183
- .custom-card {{
184
- background-color: white;
185
- border-radius: 10px;
186
- padding: 20px;
187
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
188
- margin-bottom: 20px;
189
- }}
190
-
191
- .header-container {{
192
- display: flex;
193
- justify-content: space-between;
194
- align-items: center;
195
- margin-bottom: 2rem;
196
- padding-bottom: 1rem;
197
- border-bottom: 1px solid {self.COLORS['border']};
198
- }}
199
-
200
- .header-title {{
201
- color: {self.COLORS['primary']};
202
- font-size: {self.FONT_SIZES['3xl']};
203
- margin: 0;
204
- }}
205
-
206
- .header-subtitle {{
207
- color: {self.COLORS['dark']};
208
- font-size: {self.FONT_SIZES['lg']};
209
- margin: 0;
210
- }}
211
-
212
- .header-actions {{
213
- display: flex;
214
- gap: 10px;
215
- }}
216
-
217
- /* تنسيق الجداول */
218
- div[data-testid="stTable"] table {{
219
- width: 100%;
220
- border-collapse: collapse;
221
- }}
222
-
223
- div[data-testid="stTable"] thead tr th {{
224
- background-color: {self.COLORS['primary']};
225
- color: white;
226
- text-align: right;
227
- padding: 12px;
228
- }}
229
-
230
- div[data-testid="stTable"] tbody tr:nth-child(even) {{
231
- background-color: {self.COLORS['light']};
232
- }}
233
-
234
- div[data-testid="stTable"] tbody tr:hover {{
235
- background-color: {self.COLORS['primary'] + '10'};
236
- }}
237
-
238
- div[data-testid="stTable"] tbody tr td {{
239
- padding: 10px;
240
- text-align: right;
241
- }}
242
-
243
- /* تنسيق النماذج */
244
- div[data-testid="stForm"] {{
245
- background-color: white;
246
- border-radius: 10px;
247
- padding: 20px;
248
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
249
- }}
250
-
251
- button[kind="primaryFormSubmit"] {{
252
- background-color: {self.COLORS['primary']};
253
- color: white;
254
- }}
255
-
256
- button[kind="secondaryFormSubmit"] {{
257
- background-color: {self.COLORS['light']};
258
- color: {self.COLORS['dark']};
259
- border: 1px solid {self.COLORS['border']};
260
- }}
261
-
262
- /* تنسيق الرسوم البيانية */
263
- div[data-testid="stVegaLiteChart"] {{
264
- background-color: white;
265
- border-radius: 10px;
266
- padding: 20px;
267
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
268
- }}
269
- """
270
-
271
- # تطبيق CSS
272
- st.markdown(f'<style>{css}</style>', unsafe_allow_html=True)
273
-
274
- def apply_theme_colors(self):
275
- """تطبيق ألوان السمة الحالية"""
276
- # تحديد ألوان السمة بناءً على الوضع
277
- if self.theme_mode == "dark":
278
- self.COLORS['background'] = '#121212'
279
- self.COLORS['text'] = '#FFFFFF'
280
- self.COLORS['border'] = '#333333'
281
- else:
282
- self.COLORS['background'] = '#FFFFFF'
283
- self.COLORS['text'] = '#212121'
284
- self.COLORS['border'] = '#E0E0E0'
285
-
286
- # تطبيق CSS للسمة
287
- theme_css = f"""
288
- body {{
289
- background-color: {self.COLORS['background']};
290
- color: {self.COLORS['text']};
291
- }}
292
- """
293
-
294
- st.markdown(f'<style>{theme_css}</style>', unsafe_allow_html=True)
295
-
296
- def toggle_theme(self):
297
- """تبديل وضع السمة بين الفاتح والداكن"""
298
- if self.theme_mode == "light":
299
- self.theme_mode = "dark"
300
- else:
301
- self.theme_mode = "light"
302
-
303
- self.apply_theme_colors()
304
-
305
- def create_sidebar(self, menu_items):
306
- """إنشاء الشريط الجانبي مع قائمة العناصر"""
307
- with st.sidebar:
308
- # إضافة الشعار
309
- st.markdown(
310
- f"""
311
- <div style="text-align: center; margin-bottom: 20px;">
312
- <h2 style="color: {self.COLORS['primary']};">{self.page_icon} {self.page_title}</h2>
313
- </div>
314
- """,
315
- unsafe_allow_html=True
316
- )
317
-
318
- # إضافة معلومات المستخدم
319
- st.markdown(
320
- f"""
321
- <div style="text-align: center; margin-bottom: 20px;">
322
- <div style="width: 60px; height: 60px; border-radius: 50%; background-color: {self.COLORS['primary']}; color: white; display: flex; align-items: center; justify-content: center; margin: 0 auto; font-size: 24px; font-weight: bold;">
323
- م
324
- </div>
325
- <p style="margin-top: 10px; font-weight: bold;">مهندس تامر الجوهري</p>
326
- <p style="margin-top: -15px; font-size: 0.8rem; color: #666;">مدير المشاريع</p>
327
- </div>
328
- """,
329
- unsafe_allow_html=True
330
- )
331
-
332
- st.divider()
333
-
334
- # إنشاء القائمة
335
- selected = st.radio(
336
- "القائمة الرئيسية",
337
- [item["name"] for item in menu_items],
338
- format_func=lambda x: x,
339
- label_visibility="collapsed"
340
- )
341
-
342
- st.divider()
343
-
344
- # إضافة معلومات النظام
345
- st.markdown(
346
- """
347
- <div style="position: absolute; bottom: 20px; left: 20px; right: 20px; text-align: center;">
348
- <p style="font-size: 0.8rem; color: #666;">نظام تحليل المناقصات | الإصدار 2.0.0</p>
349
- <p style="font-size: 0.7rem; color: #888;">© 2025 جميع الحقوق محفوظة</p>
350
- </div>
351
- """,
352
- unsafe_allow_html=True
353
- )
354
-
355
- return selected
356
-
357
- def create_header(self, title, subtitle=None, show_actions=True):
358
- """إنشاء ترويسة الصفحة"""
359
- # إنشاء معرفات فريدة للأزرار
360
- add_button_key = f"add_button_{title}"
361
- update_button_key = f"update_button_{title}"
362
-
363
- col1, col2 = st.columns([3, 1])
364
-
365
- with col1:
366
- st.markdown(f'<h1 class="header-title">{title}</h1>', unsafe_allow_html=True)
367
- if subtitle:
368
- st.markdown(f'<p class="header-subtitle">{subtitle}</p>', unsafe_allow_html=True)
369
-
370
- if show_actions:
371
- with col2:
372
- col2_1, col2_2 = st.columns(2)
373
- with col2_1:
374
- st.button("إضافة جديد", key=add_button_key)
375
- with col2_2:
376
- st.button("تحديث", key=update_button_key)
377
-
378
- st.divider()
379
-
380
- def create_metric_card(self, label, value, change=None, color=None):
381
- """إنشاء بطاقة مقياس"""
382
- if color is None:
383
- color = self.COLORS['primary']
384
-
385
- change_html = ""
386
- if change is not None:
387
- if change.startswith("+"):
388
- change_class = "metric-change-positive"
389
- change_icon = "↑"
390
- elif change.startswith("-"):
391
- change_class = "metric-change-negative"
392
- change_icon = "↓"
393
- else:
394
- change_class = ""
395
- change_icon = ""
396
-
397
- change_html = f'<div class="metric-change {change_class}">{change_icon} {change}</div>'
398
-
399
- st.markdown(
400
- f"""
401
- <div class="metric-card" style="border-top: 4px solid {color};">
402
- <div class="metric-label">{label}</div>
403
- <div class="metric-value" style="color: {color};">{value}</div>
404
- {change_html}
405
- </div>
406
- """,
407
- unsafe_allow_html=True
408
- )
409
-
410
- def create_card(self, title, content, color=None):
411
- """إنشاء بطاقة عامة"""
412
- if color is None:
413
- color = self.COLORS['primary']
414
-
415
- st.markdown(
416
- f"""
417
- <div class="custom-card" style="border-top: 4px solid {color};">
418
- <h3 style="color: {color}; margin-top: 0;">{title}</h3>
419
- <div>{content}</div>
420
- </div>
421
- """,
422
- unsafe_allow_html=True
423
- )
424
-
425
- def create_button(self, label, color=None, icon=None, key=None):
426
- """إنشاء زر مخصص"""
427
- if color is None:
428
- color = self.COLORS['primary']
429
-
430
- # إنشاء معرف فريد للزر إذا لم يتم توفيره
431
- if key is None:
432
- key = f"button_{label}_{hash(label)}"
433
-
434
- icon_html = f"{icon} " if icon else ""
435
-
436
- return st.button(
437
- f"{icon_html}{label}",
438
- key=key
439
- )
440
-
441
- def create_tabs(self, tab_names):
442
- """إنشاء تبويبات"""
443
- return st.tabs(tab_names)
444
-
445
- def create_expander(self, title, expanded=False, key=None):
446
- """إنشاء عنصر قابل للتوسيع"""
447
- # إنشاء معرف فريد للعنصر إذا لم يتم توفيره
448
- if key is None:
449
- key = f"expander_{title}_{hash(title)}"
450
-
451
- return st.expander(title, expanded=expanded, key=key)
452
-
453
- def create_data_table(self, data, use_container_width=True, hide_index=True):
454
- """إنشاء جدول بيانات"""
455
- return st.dataframe(data, use_container_width=use_container_width, hide_index=hide_index)
456
-
457
- def create_chart(self, chart_type, data, **kwargs):
458
- """إنشاء رسم بياني"""
459
- if chart_type == "bar":
460
- return st.bar_chart(data, **kwargs)
461
- elif chart_type == "line":
462
- return st.line_chart(data, **kwargs)
463
- elif chart_type == "area":
464
- return st.area_chart(data, **kwargs)
465
- else:
466
- return st.bar_chart(data, **kwargs)
467
-
468
- def create_form(self, title, key=None):
469
- """إنشاء نموذج"""
470
- # إنشاء معرف فريد للنموذج إذا لم يتم توفيره
471
- if key is None:
472
- key = f"form_{title}_{hash(title)}"
473
-
474
- return st.form(key=key)
475
-
476
- def create_file_uploader(self, label, types=None, key=None):
477
- """إنشاء أداة رفع الملفات"""
478
- # إنشاء معرف فريد لأداة رفع الملفات إذا لم يتم توفيره
479
- if key is None:
480
- key = f"file_uploader_{label}_{hash(label)}"
481
-
482
- return st.file_uploader(label, type=types, key=key)
483
-
484
- def create_date_input(self, label, value=None, key=None):
485
- """إنشاء حقل إدخال تاريخ"""
486
- # إنشاء معرف فريد لحقل إدخال التاريخ إذا لم يتم توفيره
487
- if key is None:
488
- key = f"date_input_{label}_{hash(label)}"
489
-
490
- return st.date_input(label, value=value, key=key)
491
-
492
- def create_select_box(self, label, options, index=0, key=None):
493
- """إنشاء قائمة منسدلة"""
494
- # إنشاء معرف فريد للقائمة المنسدلة إذا لم يتم توفيره
495
- if key is None:
496
- key = f"select_box_{label}_{hash(label)}"
497
-
498
- return st.selectbox(label, options, index=index, key=key)
499
-
500
- def create_multi_select(self, label, options, default=None, key=None):
501
- """إنشاء قائمة اختيار متعدد"""
502
- # إنشاء معرف فريد لقائمة الاختيار المتعدد إذا لم يتم توفيره
503
- if key is None:
504
- key = f"multi_select_{label}_{hash(label)}"
505
-
506
- return st.multiselect(label, options, default=default, key=key)
507
-
508
- def create_slider(self, label, min_value, max_value, value=None, step=1, key=None):
509
- """إنشاء شريط تمرير"""
510
- # إنشاء معرف فريد لشريط التمرير إذا لم يتم توفيره
511
- if key is None:
512
- key = f"slider_{label}_{hash(label)}"
513
-
514
- return st.slider(label, min_value=min_value, max_value=max_value, value=value, step=step, key=key)
515
-
516
- def create_text_input(self, label, value="", key=None):
517
- """إنشاء حقل إدخال نصي"""
518
- # إنشاء معرف فريد لحقل الإدخال النصي إذا لم يتم توفيره
519
- if key is None:
520
- key = f"text_input_{label}_{hash(label)}"
521
-
522
- return st.text_input(label, value=value, key=key)
523
-
524
- def create_text_area(self, label, value="", height=None, key=None):
525
- """إنشاء منطقة نص"""
526
- # إنشاء معرف فريد لمنطقة النص إذا لم يتم توفيره
527
- if key is None:
528
- key = f"text_area_{label}_{hash(label)}"
529
-
530
- return st.text_area(label, value=value, height=height, key=key)
531
-
532
- def create_number_input(self, label, min_value=None, max_value=None, value=0, step=1, key=None):
533
- """إنشاء حقل إدخال رقمي"""
534
- # إنشاء معرف فريد لحقل الإدخال الرقمي إذا لم يتم توفيره
535
- if key is None:
536
- key = f"number_input_{label}_{hash(label)}"
537
-
538
- return st.number_input(label, min_value=min_value, max_value=max_value, value=value, step=step, key=key)
539
-
540
- def create_checkbox(self, label, value=False, key=None):
541
- """إنشاء خانة اختيار"""
542
- # إنشاء معرف فريد لخانة الاختيار إذا لم يتم توفيره
543
- if key is None:
544
- key = f"checkbox_{label}_{hash(label)}"
545
-
546
- return st.checkbox(label, value=value, key=key)
547
-
548
- def create_radio(self, label, options, index=0, key=None):
549
- """إنشاء أزرار راديو"""
550
- # إنشاء معرف فريد لأزرار الراديو إذا لم يتم توفيره
551
- if key is None:
552
- key = f"radio_{label}_{hash(label)}"
553
-
554
- return st.radio(label, options, index=index, key=key)
555
-
556
- def create_progress_bar(self, value, key=None):
557
- """إنشاء شريط تقدم"""
558
- # إنشاء معرف فريد لشريط التقدم إذا لم يتم توفيره
559
- if key is None:
560
- key = f"progress_bar_{value}_{hash(str(value))}"
561
-
562
- return st.progress(value, key=key)
563
-
564
- def create_spinner(self, text="جاري التحميل..."):
565
- """إنشاء مؤشر تحميل"""
566
- return st.spinner(text)
567
-
568
- def create_success_message(self, message):
569
- """إنشاء رسالة نجاح"""
570
- return st.success(message)
571
-
572
- def create_error_message(self, message):
573
- """إنشاء رسالة خطأ"""
574
- return st.error(message)
575
-
576
- def create_warning_message(self, message):
577
- """إنشاء رسالة تحذير"""
578
- return st.warning(message)
579
-
580
- def create_info_message(self, message):
581
- """إنشاء رسالة معلومات"""
582
- return st.info(message)
 
1
+ """
2
+ محسن واجهة المستخدم - نظام تحليل المناقصات
3
+ """
4
+
5
+ import streamlit as st
6
+ import pandas as pd
7
+ import numpy as np
8
+ import base64
9
+ from pathlib import Path
10
+ import os
11
+
12
+ class UIEnhancer:
13
+ """فئة لتحسين واجهة المستخدم وتوحيد التصميم عبر النظام"""
14
+
15
+ # ألوان النظام
16
+ COLORS = {
17
+ 'primary': '#1E88E5', # أزرق
18
+ 'secondary': '#5E35B1', # بنفسجي
19
+ 'success': '#43A047', # أخضر
20
+ 'warning': '#FB8C00', # برتقالي
21
+ 'danger': '#E53935', # أحمر
22
+ 'info': '#00ACC1', # سماوي
23
+ 'light': '#F5F5F5', # رمادي فاتح
24
+ 'dark': '#212121', # رمادي داكن
25
+ 'accent': '#FF4081', # وردي
26
+ 'background': '#FFFFFF', # أبيض
27
+ 'text': '#212121', # أسود
28
+ 'border': '#E0E0E0' # رمادي حدود
29
+ }
30
+
31
+ # أحجام الخطوط
32
+ FONT_SIZES = {
33
+ 'xs': '0.75rem',
34
+ 'sm': '0.875rem',
35
+ 'md': '1rem',
36
+ 'lg': '1.125rem',
37
+ 'xl': '1.25rem',
38
+ '2xl': '1.5rem',
39
+ '3xl': '1.875rem',
40
+ '4xl': '2.25rem',
41
+ '5xl': '3rem'
42
+ }
43
+
44
+ def __init__(self, page_title="نظام تحليل المناقصات", page_icon="📊"):
45
+ """تهيئة محسن واجهة المستخدم"""
46
+ self.page_title = page_title
47
+ self.page_icon = page_icon
48
+ self.theme_mode = "light" # الوضع الافتراضي هو الوضع الفاتح
49
+
50
+ # تهيئة متغير السمة في حالة الجلسة إذا لم يكن موجوداً
51
+ if 'theme' not in st.session_state:
52
+ st.session_state.theme = 'light'
53
+
54
+ def apply_global_styles(self):
55
+ """تطبيق التنسيقات العامة على الصفحة"""
56
+ # تعريف CSS العام
57
+ css = f"""
58
+ @import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700&display=swap');
59
+
60
+ * {{
61
+ font-family: 'Tajawal', sans-serif;
62
+ direction: rtl;
63
+ }}
64
+
65
+ h1, h2, h3, h4, h5, h6 {{
66
+ font-family: 'Tajawal', sans-serif;
67
+ font-weight: 700;
68
+ color: {self.COLORS['dark']};
69
+ }}
70
+
71
+ .module-title {{
72
+ color: {self.COLORS['primary']};
73
+ font-size: {self.FONT_SIZES['3xl']};
74
+ margin-bottom: 1rem;
75
+ border-bottom: 2px solid {self.COLORS['primary']};
76
+ padding-bottom: 0.5rem;
77
+ }}
78
+
79
+ .stTabs [data-baseweb="tab-list"] {{
80
+ gap: 2px;
81
+ }}
82
+
83
+ .stTabs [data-baseweb="tab"] {{
84
+ height: 50px;
85
+ white-space: pre-wrap;
86
+ background-color: {self.COLORS['light']};
87
+ border-radius: 4px 4px 0 0;
88
+ gap: 1px;
89
+ padding-top: 10px;
90
+ padding-bottom: 10px;
91
+ }}
92
+
93
+ .stTabs [aria-selected="true"] {{
94
+ background-color: {self.COLORS['primary']};
95
+ color: white;
96
+ }}
97
+
98
+ div[data-testid="stSidebarNav"] li div a span {{
99
+ direction: rtl;
100
+ text-align: right;
101
+ font-family: 'Tajawal', sans-serif;
102
+ }}
103
+
104
+ div[data-testid="stSidebarNav"] {{
105
+ background-color: {self.COLORS['light']};
106
+ }}
107
+
108
+ div[data-testid="stSidebarNav"] li div {{
109
+ margin-right: 0;
110
+ margin-left: auto;
111
+ }}
112
+
113
+ div[data-testid="stSidebarNav"] li div a {{
114
+ padding-right: 10px;
115
+ padding-left: 0;
116
+ }}
117
+
118
+ div[data-testid="stSidebarNav"] li div a:hover {{
119
+ background-color: {self.COLORS['primary'] + '20'};
120
+ }}
121
+
122
+ div[data-testid="stSidebarNav"] li div[aria-selected="true"] {{
123
+ background-color: {self.COLORS['primary'] + '40'};
124
+ }}
125
+
126
+ div[data-testid="stSidebarNav"] li div[aria-selected="true"] a span {{
127
+ color: {self.COLORS['primary']};
128
+ font-weight: 500;
129
+ }}
130
+
131
+ .metric-card {{
132
+ background-color: white;
133
+ border-radius: 10px;
134
+ padding: 20px;
135
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
136
+ text-align: center;
137
+ transition: transform 0.3s ease;
138
+ }}
139
+
140
+ .metric-card:hover {{
141
+ transform: translateY(-5px);
142
+ }}
143
+
144
+ .metric-value {{
145
+ font-size: 2.5rem;
146
+ font-weight: 700;
147
+ margin: 10px 0;
148
+ }}
149
+
150
+ .metric-label {{
151
+ font-size: 1rem;
152
+ color: #666;
153
+ }}
154
+
155
+ .metric-change {{
156
+ font-size: 0.9rem;
157
+ margin-top: 5px;
158
+ }}
159
+
160
+ .metric-change-positive {{
161
+ color: {self.COLORS['success']};
162
+ }}
163
+
164
+ .metric-change-negative {{
165
+ color: {self.COLORS['danger']};
166
+ }}
167
+
168
+ .custom-button {{
169
+ background-color: {self.COLORS['primary']};
170
+ color: white;
171
+ border: none;
172
+ border-radius: 5px;
173
+ padding: 10px 20px;
174
+ font-size: 1rem;
175
+ cursor: pointer;
176
+ transition: background-color 0.3s ease;
177
+ }}
178
+
179
+ .custom-button:hover {{
180
+ background-color: {self.COLORS['secondary']};
181
+ }}
182
+
183
+ .custom-card {{
184
+ background-color: white;
185
+ border-radius: 10px;
186
+ padding: 20px;
187
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
188
+ margin-bottom: 20px;
189
+ }}
190
+
191
+ .header-container {{
192
+ display: flex;
193
+ justify-content: space-between;
194
+ align-items: center;
195
+ margin-bottom: 2rem;
196
+ padding-bottom: 1rem;
197
+ border-bottom: 1px solid {self.COLORS['border']};
198
+ }}
199
+
200
+ .header-title {{
201
+ color: {self.COLORS['primary']};
202
+ font-size: {self.FONT_SIZES['3xl']};
203
+ margin: 0;
204
+ }}
205
+
206
+ .header-subtitle {{
207
+ color: {self.COLORS['dark']};
208
+ font-size: {self.FONT_SIZES['lg']};
209
+ margin: 0;
210
+ }}
211
+
212
+ .header-actions {{
213
+ display: flex;
214
+ gap: 10px;
215
+ }}
216
+
217
+ /* تنسيق الجداول */
218
+ div[data-testid="stTable"] table {{
219
+ width: 100%;
220
+ border-collapse: collapse;
221
+ }}
222
+
223
+ div[data-testid="stTable"] thead tr th {{
224
+ background-color: {self.COLORS['primary']};
225
+ color: white;
226
+ text-align: right;
227
+ padding: 12px;
228
+ }}
229
+
230
+ div[data-testid="stTable"] tbody tr:nth-child(even) {{
231
+ background-color: {self.COLORS['light']};
232
+ }}
233
+
234
+ div[data-testid="stTable"] tbody tr:hover {{
235
+ background-color: {self.COLORS['primary'] + '10'};
236
+ }}
237
+
238
+ div[data-testid="stTable"] tbody tr td {{
239
+ padding: 10px;
240
+ text-align: right;
241
+ }}
242
+
243
+ /* تنسيق النماذج */
244
+ div[data-testid="stForm"] {{
245
+ background-color: white;
246
+ border-radius: 10px;
247
+ padding: 20px;
248
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
249
+ }}
250
+
251
+ button[kind="primaryFormSubmit"] {{
252
+ background-color: {self.COLORS['primary']};
253
+ color: white;
254
+ }}
255
+
256
+ button[kind="secondaryFormSubmit"] {{
257
+ background-color: {self.COLORS['light']};
258
+ color: {self.COLORS['dark']};
259
+ border: 1px solid {self.COLORS['border']};
260
+ }}
261
+
262
+ /* تنسيق الرسوم البيانية */
263
+ div[data-testid="stVegaLiteChart"] {{
264
+ background-color: white;
265
+ border-radius: 10px;
266
+ padding: 20px;
267
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
268
+ }}
269
+ """
270
+
271
+ # تطبيق CSS
272
+ st.markdown(f'<style>{css}</style>', unsafe_allow_html=True)
273
+
274
+ def apply_theme_colors(self):
275
+ """تطبيق ألوان السمة الحالية"""
276
+ # تحديد ألوان السمة بناءً على الوضع
277
+ if self.theme_mode == "dark":
278
+ self.COLORS['background'] = '#121212'
279
+ self.COLORS['text'] = '#FFFFFF'
280
+ self.COLORS['border'] = '#333333'
281
+ else:
282
+ self.COLORS['background'] = '#FFFFFF'
283
+ self.COLORS['text'] = '#212121'
284
+ self.COLORS['border'] = '#E0E0E0'
285
+
286
+ # تطبيق CSS للسمة
287
+ theme_css = f"""
288
+ body {{
289
+ background-color: {self.COLORS['background']};
290
+ color: {self.COLORS['text']};
291
+ }}
292
+ """
293
+
294
+ st.markdown(f'<style>{theme_css}</style>', unsafe_allow_html=True)
295
+
296
+ def toggle_theme(self):
297
+ """تبديل وضع السمة بين الفاتح والداكن"""
298
+ if self.theme_mode == "light":
299
+ self.theme_mode = "dark"
300
+ else:
301
+ self.theme_mode = "light"
302
+
303
+ self.apply_theme_colors()
304
+
305
+ def create_sidebar(self, menu_items):
306
+ """إنشاء الشريط الجانبي مع قائمة العناصر"""
307
+ with st.sidebar:
308
+ # إضافة الشعار
309
+ st.markdown(
310
+ f"""
311
+ <div style="text-align: center; margin-bottom: 20px;">
312
+ <h2 style="color: {self.COLORS['primary']};">{self.page_icon} {self.page_title}</h2>
313
+ </div>
314
+ """,
315
+ unsafe_allow_html=True
316
+ )
317
+
318
+ # إضافة معلومات المستخدم
319
+ st.markdown(
320
+ f"""
321
+ <div style="text-align: center; margin-bottom: 20px;">
322
+ <div style="width: 60px; height: 60px; border-radius: 50%; background-color: {self.COLORS['primary']}; color: white; display: flex; align-items: center; justify-content: center; margin: 0 auto; font-size: 24px; font-weight: bold;">
323
+ م
324
+ </div>
325
+ <p style="margin-top: 10px; font-weight: bold;">مهندس تامر الجوهري</p>
326
+ <p style="margin-top: -15px; font-size: 0.8rem; color: #666;">مدير تقنية معلومات المنطقة الشمالية </p>
327
+ </div>
328
+ """,
329
+ unsafe_allow_html=True
330
+ )
331
+
332
+ st.divider()
333
+
334
+ # إنشاء القائمة
335
+ selected = st.radio(
336
+ "القائمة الرئيسية",
337
+ [item["name"] for item in menu_items],
338
+ format_func=lambda x: x,
339
+ label_visibility="collapsed"
340
+ )
341
+
342
+ st.divider()
343
+
344
+ # إضافة معلومات النظام
345
+ st.markdown(
346
+ """
347
+ <div style="position: absolute; bottom: 20px; left: 20px; right: 20px; text-align: center;">
348
+ <p style="font-size: 0.8rem; color: #666;">نظام تحليل المناقصات | الإصدار 2.0.0</p>
349
+ <p style="font-size: 0.7rem; color: #888;">© 2025 جميع الحقوق محفوظة</p>
350
+ </div>
351
+ """,
352
+ unsafe_allow_html=True
353
+ )
354
+
355
+ return selected
356
+
357
+ def create_header(self, title, subtitle=None, show_actions=True):
358
+ """إنشاء ترويسة الصفحة"""
359
+ # إنشاء معرفات فريدة للأزرار
360
+ add_button_key = f"add_button_{title}"
361
+ update_button_key = f"update_button_{title}"
362
+
363
+ col1, col2 = st.columns([3, 1])
364
+
365
+ with col1:
366
+ st.markdown(f'<h1 class="header-title">{title}</h1>', unsafe_allow_html=True)
367
+ if subtitle:
368
+ st.markdown(f'<p class="header-subtitle">{subtitle}</p>', unsafe_allow_html=True)
369
+
370
+ if show_actions:
371
+ with col2:
372
+ col2_1, col2_2 = st.columns(2)
373
+ with col2_1:
374
+ st.button("إضافة جديد", key=add_button_key)
375
+ with col2_2:
376
+ st.button("تحديث", key=update_button_key)
377
+
378
+ st.divider()
379
+
380
+ def create_metric_card(self, label, value, change=None, color=None):
381
+ """إنشاء بطاقة مقياس"""
382
+ if color is None:
383
+ color = self.COLORS['primary']
384
+
385
+ change_html = ""
386
+ if change is not None:
387
+ if change.startswith("+"):
388
+ change_class = "metric-change-positive"
389
+ change_icon = "↑"
390
+ elif change.startswith("-"):
391
+ change_class = "metric-change-negative"
392
+ change_icon = "↓"
393
+ else:
394
+ change_class = ""
395
+ change_icon = ""
396
+
397
+ change_html = f'<div class="metric-change {change_class}">{change_icon} {change}</div>'
398
+
399
+ st.markdown(
400
+ f"""
401
+ <div class="metric-card" style="border-top: 4px solid {color};">
402
+ <div class="metric-label">{label}</div>
403
+ <div class="metric-value" style="color: {color};">{value}</div>
404
+ {change_html}
405
+ </div>
406
+ """,
407
+ unsafe_allow_html=True
408
+ )
409
+
410
+ def create_card(self, title, content, color=None):
411
+ """إنشاء بطاقة عامة"""
412
+ if color is None:
413
+ color = self.COLORS['primary']
414
+
415
+ st.markdown(
416
+ f"""
417
+ <div class="custom-card" style="border-top: 4px solid {color};">
418
+ <h3 style="color: {color}; margin-top: 0;">{title}</h3>
419
+ <div>{content}</div>
420
+ </div>
421
+ """,
422
+ unsafe_allow_html=True
423
+ )
424
+
425
+ def create_button(self, label, color=None, icon=None, key=None):
426
+ """إنشاء زر مخصص"""
427
+ if color is None:
428
+ color = self.COLORS['primary']
429
+
430
+ # إنشاء معرف فريد للزر إذا لم يتم توفيره
431
+ if key is None:
432
+ key = f"button_{label}_{hash(label)}"
433
+
434
+ icon_html = f"{icon} " if icon else ""
435
+
436
+ return st.button(
437
+ f"{icon_html}{label}",
438
+ key=key
439
+ )
440
+
441
+ def create_tabs(self, tab_names):
442
+ """إنشاء تبويبات"""
443
+ return st.tabs(tab_names)
444
+
445
+ def create_expander(self, title, expanded=False, key=None):
446
+ """إنشاء عنصر قابل للتوسيع"""
447
+ # إنشاء معرف فريد للعنصر إذا لم يتم توفيره
448
+ if key is None:
449
+ key = f"expander_{title}_{hash(title)}"
450
+
451
+ return st.expander(title, expanded=expanded, key=key)
452
+
453
+ def create_data_table(self, data, use_container_width=True, hide_index=True):
454
+ """إنشاء جدول بيانات"""
455
+ return st.dataframe(data, use_container_width=use_container_width, hide_index=hide_index)
456
+
457
+ def create_chart(self, chart_type, data, **kwargs):
458
+ """إنشاء رسم بياني"""
459
+ if chart_type == "bar":
460
+ return st.bar_chart(data, **kwargs)
461
+ elif chart_type == "line":
462
+ return st.line_chart(data, **kwargs)
463
+ elif chart_type == "area":
464
+ return st.area_chart(data, **kwargs)
465
+ else:
466
+ return st.bar_chart(data, **kwargs)
467
+
468
+ def create_form(self, title, key=None):
469
+ """إنشاء نموذج"""
470
+ # إنشاء معرف فريد للنموذج إذا لم يتم توفيره
471
+ if key is None:
472
+ key = f"form_{title}_{hash(title)}"
473
+
474
+ return st.form(key=key)
475
+
476
+ def create_file_uploader(self, label, types=None, key=None):
477
+ """إنشاء أداة رفع الملفات"""
478
+ # إنشاء معرف فريد لأداة رفع الملفات إذا لم يتم توفيره
479
+ if key is None:
480
+ key = f"file_uploader_{label}_{hash(label)}"
481
+
482
+ return st.file_uploader(label, type=types, key=key)
483
+
484
+ def create_date_input(self, label, value=None, key=None):
485
+ """إنشاء حقل إدخال تاريخ"""
486
+ # إنشاء معرف فريد لحقل إدخال التاريخ إذا لم يتم توفيره
487
+ if key is None:
488
+ key = f"date_input_{label}_{hash(label)}"
489
+
490
+ return st.date_input(label, value=value, key=key)
491
+
492
+ def create_select_box(self, label, options, index=0, key=None):
493
+ """إنشاء قائمة منسدلة"""
494
+ # إنشاء معرف فريد للقائمة المنسدلة إذا لم يتم توفيره
495
+ if key is None:
496
+ key = f"select_box_{label}_{hash(label)}"
497
+
498
+ return st.selectbox(label, options, index=index, key=key)
499
+
500
+ def create_multi_select(self, label, options, default=None, key=None):
501
+ """إنشاء قائمة اختيار متعدد"""
502
+ # إنشاء معرف فريد لقائمة الاختيار المتعدد إذا لم يتم توفيره
503
+ if key is None:
504
+ key = f"multi_select_{label}_{hash(label)}"
505
+
506
+ return st.multiselect(label, options, default=default, key=key)
507
+
508
+ def create_slider(self, label, min_value, max_value, value=None, step=1, key=None):
509
+ """إنشاء شريط تمرير"""
510
+ # إنشاء معرف فريد لشريط التمرير إذا لم يتم توفيره
511
+ if key is None:
512
+ key = f"slider_{label}_{hash(label)}"
513
+
514
+ return st.slider(label, min_value=min_value, max_value=max_value, value=value, step=step, key=key)
515
+
516
+ def create_text_input(self, label, value="", key=None):
517
+ """إنشاء حقل إدخال نصي"""
518
+ # إنشاء معرف فريد لحقل الإدخال النصي إذا لم يتم توفيره
519
+ if key is None:
520
+ key = f"text_input_{label}_{hash(label)}"
521
+
522
+ return st.text_input(label, value=value, key=key)
523
+
524
+ def create_text_area(self, label, value="", height=None, key=None):
525
+ """إنشاء منطقة نص"""
526
+ # إنشاء معرف فريد لمنطقة النص إذا لم يتم توفيره
527
+ if key is None:
528
+ key = f"text_area_{label}_{hash(label)}"
529
+
530
+ return st.text_area(label, value=value, height=height, key=key)
531
+
532
+ def create_number_input(self, label, min_value=None, max_value=None, value=0, step=1, key=None):
533
+ """إنشاء حقل إدخال رقمي"""
534
+ # إنشاء معرف فريد لحقل الإدخال الرقمي إذا لم يتم توفيره
535
+ if key is None:
536
+ key = f"number_input_{label}_{hash(label)}"
537
+
538
+ return st.number_input(label, min_value=min_value, max_value=max_value, value=value, step=step, key=key)
539
+
540
+ def create_checkbox(self, label, value=False, key=None):
541
+ """إنشاء خانة اختيار"""
542
+ # إنشاء معرف فريد لخانة الاختيار إذا لم يتم توفيره
543
+ if key is None:
544
+ key = f"checkbox_{label}_{hash(label)}"
545
+
546
+ return st.checkbox(label, value=value, key=key)
547
+
548
+ def create_radio(self, label, options, index=0, key=None):
549
+ """إنشاء أزرار راديو"""
550
+ # إنشاء معرف فريد لأزرار الراديو إذا لم يتم توفيره
551
+ if key is None:
552
+ key = f"radio_{label}_{hash(label)}"
553
+
554
+ return st.radio(label, options, index=index, key=key)
555
+
556
+ def create_progress_bar(self, value, key=None):
557
+ """إنشاء شريط تقدم"""
558
+ # إنشاء معرف فريد لشريط التقدم إذا لم يتم توفيره
559
+ if key is None:
560
+ key = f"progress_bar_{value}_{hash(str(value))}"
561
+
562
+ return st.progress(value, key=key)
563
+
564
+ def create_spinner(self, text="جاري التحميل..."):
565
+ """إنشاء مؤشر تحميل"""
566
+ return st.spinner(text)
567
+
568
+ def create_success_message(self, message):
569
+ """إنشاء رسالة نجاح"""
570
+ return st.success(message)
571
+
572
+ def create_error_message(self, message):
573
+ """إنشاء رسالة خطأ"""
574
+ return st.error(message)
575
+
576
+ def create_warning_message(self, message):
577
+ """إنشاء رسالة تحذير"""
578
+ return st.warning(message)
579
+
580
+ def create_info_message(self, message):
581
+ """إنشاء رسالة معلومات"""
582
+ return st.info(message)