Update app.py
Browse files
app.py
CHANGED
|
@@ -182,6 +182,19 @@ p, span, div {
|
|
| 182 |
background: #3d3d5c !important;
|
| 183 |
border-color: #6366f1 !important;
|
| 184 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
"""
|
| 186 |
|
| 187 |
# ==================================================
|
|
@@ -305,6 +318,69 @@ def format_time(timestamp):
|
|
| 305 |
except:
|
| 306 |
return "غير معروف"
|
| 307 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
# ==================================================
|
| 309 |
# إضافة رأي
|
| 310 |
# ==================================================
|
|
@@ -414,7 +490,7 @@ def add_testimonial(name, role, content, rating, request: gr.Request):
|
|
| 414 |
# جلب الآراء
|
| 415 |
# ==================================================
|
| 416 |
|
| 417 |
-
def get_testimonials(
|
| 418 |
conn = get_connection()
|
| 419 |
c = conn.cursor()
|
| 420 |
|
|
@@ -422,16 +498,6 @@ def get_testimonials(filter_type="all", sort_by="newest", search_query="", page=
|
|
| 422 |
conditions = []
|
| 423 |
params = []
|
| 424 |
|
| 425 |
-
# فلترة
|
| 426 |
-
if filter_type == "highlight":
|
| 427 |
-
conditions.append("is_highlighted = 1")
|
| 428 |
-
elif filter_type == "rating_5":
|
| 429 |
-
conditions.append("rating = 5")
|
| 430 |
-
elif filter_type == "rating_4":
|
| 431 |
-
conditions.append("rating = 4")
|
| 432 |
-
elif filter_type == "rating_3":
|
| 433 |
-
conditions.append("rating = 3")
|
| 434 |
-
|
| 435 |
# بحث
|
| 436 |
if search_query:
|
| 437 |
conditions.append("(content LIKE ? OR name LIKE ?)")
|
|
@@ -440,11 +506,8 @@ def get_testimonials(filter_type="all", sort_by="newest", search_query="", page=
|
|
| 440 |
if conditions:
|
| 441 |
query += " WHERE " + " AND ".join(conditions)
|
| 442 |
|
| 443 |
-
# ترتيب
|
| 444 |
-
|
| 445 |
-
query += " ORDER BY timestamp DESC"
|
| 446 |
-
else:
|
| 447 |
-
query += " ORDER BY timestamp ASC"
|
| 448 |
|
| 449 |
offset = (page - 1) * per_page
|
| 450 |
query += " LIMIT ? OFFSET ?"
|
|
@@ -566,8 +629,8 @@ def render_card(row):
|
|
| 566 |
# تحديث العرض
|
| 567 |
# ==================================================
|
| 568 |
|
| 569 |
-
def update_display(
|
| 570 |
-
rows, total, total_pages = get_testimonials(
|
| 571 |
|
| 572 |
if not rows:
|
| 573 |
return """
|
|
@@ -783,6 +846,9 @@ def create_interface():
|
|
| 783 |
</div>
|
| 784 |
""")
|
| 785 |
|
|
|
|
|
|
|
|
|
|
| 786 |
# ===== المحتوى الرئيسي =====
|
| 787 |
with gr.Row(equal_height=False):
|
| 788 |
|
|
@@ -853,29 +919,6 @@ def create_interface():
|
|
| 853 |
with gr.Column(scale=2, min_width=400):
|
| 854 |
gr.Markdown("### 💬 آراء المستخدمين")
|
| 855 |
|
| 856 |
-
with gr.Row():
|
| 857 |
-
filter_dropdown = gr.Dropdown(
|
| 858 |
-
choices=[
|
| 859 |
-
("الكل", "all"),
|
| 860 |
-
("المميزة", "highlight"),
|
| 861 |
-
("تقييم 5 نجوم", "rating_5"),
|
| 862 |
-
("تقييم 4 نجوم", "rating_4"),
|
| 863 |
-
("تقييم 3 نجوم", "rating_3"),
|
| 864 |
-
],
|
| 865 |
-
value="all",
|
| 866 |
-
label="🔍 فلترة",
|
| 867 |
-
scale=1
|
| 868 |
-
)
|
| 869 |
-
sort_dropdown = gr.Dropdown(
|
| 870 |
-
choices=[
|
| 871 |
-
("الأحدث", "newest"),
|
| 872 |
-
("الأقدم", "oldest")
|
| 873 |
-
],
|
| 874 |
-
value="newest",
|
| 875 |
-
label="📊 ترتيب",
|
| 876 |
-
scale=1
|
| 877 |
-
)
|
| 878 |
-
|
| 879 |
# شريط البحث
|
| 880 |
search_input = gr.Textbox(
|
| 881 |
placeholder="🔎 بحث في الآراء...",
|
|
@@ -910,41 +953,36 @@ def create_interface():
|
|
| 910 |
|
| 911 |
# ===== الأحداث =====
|
| 912 |
|
| 913 |
-
def
|
| 914 |
-
|
| 915 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 916 |
|
| 917 |
# زر النشر
|
| 918 |
submit_btn.click(
|
| 919 |
fn=add_testimonial,
|
| 920 |
inputs=[name_input, role_input, content_input, rating_input],
|
| 921 |
outputs=[status_output, name_input, role_input, testimonials_display, char_count]
|
|
|
|
|
|
|
|
|
|
| 922 |
)
|
| 923 |
|
| 924 |
# زر التحديث
|
| 925 |
refresh_btn.click(
|
| 926 |
fn=full_update,
|
| 927 |
-
inputs=[
|
| 928 |
-
outputs=[testimonials_display]
|
| 929 |
-
)
|
| 930 |
-
|
| 931 |
-
# تغيير الفلترة/الترتيب/البحث
|
| 932 |
-
filter_dropdown.change(
|
| 933 |
-
fn=full_update,
|
| 934 |
-
inputs=[filter_dropdown, sort_dropdown, search_input],
|
| 935 |
-
outputs=[testimonials_display]
|
| 936 |
-
)
|
| 937 |
-
|
| 938 |
-
sort_dropdown.change(
|
| 939 |
-
fn=full_update,
|
| 940 |
-
inputs=[filter_dropdown, sort_dropdown, search_input],
|
| 941 |
-
outputs=[testimonials_display]
|
| 942 |
)
|
| 943 |
|
|
|
|
| 944 |
search_input.change(
|
| 945 |
fn=full_update,
|
| 946 |
-
inputs=[
|
| 947 |
-
outputs=[testimonials_display]
|
| 948 |
)
|
| 949 |
|
| 950 |
# زر التصدير
|
|
@@ -966,8 +1004,8 @@ def create_interface():
|
|
| 966 |
# تحميل أولي
|
| 967 |
demo.load(
|
| 968 |
fn=full_update,
|
| 969 |
-
inputs=[
|
| 970 |
-
outputs=[testimonials_display]
|
| 971 |
)
|
| 972 |
|
| 973 |
return demo
|
|
|
|
| 182 |
background: #3d3d5c !important;
|
| 183 |
border-color: #6366f1 !important;
|
| 184 |
}
|
| 185 |
+
|
| 186 |
+
/* تحسين الإحصائيات */
|
| 187 |
+
.stat-card {
|
| 188 |
+
background: #1a1a2e !important;
|
| 189 |
+
border: 1px solid #2d2d44 !important;
|
| 190 |
+
transition: all 0.3s ease;
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
.stat-card:hover {
|
| 194 |
+
transform: translateY(-2px);
|
| 195 |
+
border-color: #6366f1 !important;
|
| 196 |
+
box-shadow: 0 4px 20px rgba(99, 102, 241, 0.1) !important;
|
| 197 |
+
}
|
| 198 |
"""
|
| 199 |
|
| 200 |
# ==================================================
|
|
|
|
| 318 |
except:
|
| 319 |
return "غير معروف"
|
| 320 |
|
| 321 |
+
# ==================================================
|
| 322 |
+
# الإحصائيات
|
| 323 |
+
# ==================================================
|
| 324 |
+
|
| 325 |
+
def get_stats():
|
| 326 |
+
"""جلب الإحصائيات"""
|
| 327 |
+
conn = get_connection()
|
| 328 |
+
c = conn.cursor()
|
| 329 |
+
|
| 330 |
+
try:
|
| 331 |
+
c.execute("SELECT COUNT(*) FROM testimonials")
|
| 332 |
+
total = c.fetchone()[0]
|
| 333 |
+
|
| 334 |
+
c.execute("SELECT AVG(rating) FROM testimonials")
|
| 335 |
+
avg = c.fetchone()[0]
|
| 336 |
+
avg_rating = round(avg, 1) if avg else 0
|
| 337 |
+
|
| 338 |
+
except Exception as e:
|
| 339 |
+
logger.error(f"خطأ في الإحصائيات: {e}")
|
| 340 |
+
return {"total": 0, "avg_rating": 0}
|
| 341 |
+
finally:
|
| 342 |
+
conn.close()
|
| 343 |
+
|
| 344 |
+
return {
|
| 345 |
+
"total": total,
|
| 346 |
+
"avg_rating": avg_rating
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
def render_stats(stats):
|
| 350 |
+
"""عرض الإحصائيات"""
|
| 351 |
+
return f"""
|
| 352 |
+
<div style="
|
| 353 |
+
display:grid;
|
| 354 |
+
grid-template-columns:repeat(auto-fit, minmax(200px, 1fr));
|
| 355 |
+
gap:1rem;
|
| 356 |
+
margin-bottom:1.5rem;
|
| 357 |
+
">
|
| 358 |
+
<div class="stat-card" style="
|
| 359 |
+
background:#1a1a2e;
|
| 360 |
+
padding:1.5rem;
|
| 361 |
+
border-radius:16px;
|
| 362 |
+
text-align:center;
|
| 363 |
+
border:1px solid #2d2d44;
|
| 364 |
+
transition: all 0.3s ease;
|
| 365 |
+
">
|
| 366 |
+
<div style="font-size:3rem;font-weight:700;color:#818cf8;">{stats['total']}</div>
|
| 367 |
+
<div style="color:#94a3b8;font-size:0.95rem;">📝 عدد الآراء</div>
|
| 368 |
+
</div>
|
| 369 |
+
|
| 370 |
+
<div class="stat-card" style="
|
| 371 |
+
background:#1a1a2e;
|
| 372 |
+
padding:1.5rem;
|
| 373 |
+
border-radius:16px;
|
| 374 |
+
text-align:center;
|
| 375 |
+
border:1px solid #2d2d44;
|
| 376 |
+
transition: all 0.3s ease;
|
| 377 |
+
">
|
| 378 |
+
<div style="font-size:3rem;font-weight:700;color:#fbbf24;">{stats['avg_rating']}</div>
|
| 379 |
+
<div style="color:#94a3b8;font-size:0.95rem;">⭐ متوسط التقييم</div>
|
| 380 |
+
</div>
|
| 381 |
+
</div>
|
| 382 |
+
"""
|
| 383 |
+
|
| 384 |
# ==================================================
|
| 385 |
# إضافة رأي
|
| 386 |
# ==================================================
|
|
|
|
| 490 |
# جلب الآراء
|
| 491 |
# ==================================================
|
| 492 |
|
| 493 |
+
def get_testimonials(search_query="", page=1, per_page=5):
|
| 494 |
conn = get_connection()
|
| 495 |
c = conn.cursor()
|
| 496 |
|
|
|
|
| 498 |
conditions = []
|
| 499 |
params = []
|
| 500 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 501 |
# بحث
|
| 502 |
if search_query:
|
| 503 |
conditions.append("(content LIKE ? OR name LIKE ?)")
|
|
|
|
| 506 |
if conditions:
|
| 507 |
query += " WHERE " + " AND ".join(conditions)
|
| 508 |
|
| 509 |
+
# ترتيب من الأحدث
|
| 510 |
+
query += " ORDER BY timestamp DESC"
|
|
|
|
|
|
|
|
|
|
| 511 |
|
| 512 |
offset = (page - 1) * per_page
|
| 513 |
query += " LIMIT ? OFFSET ?"
|
|
|
|
| 629 |
# تحديث العرض
|
| 630 |
# ==================================================
|
| 631 |
|
| 632 |
+
def update_display(search_query="", page=1):
|
| 633 |
+
rows, total, total_pages = get_testimonials(search_query, page)
|
| 634 |
|
| 635 |
if not rows:
|
| 636 |
return """
|
|
|
|
| 846 |
</div>
|
| 847 |
""")
|
| 848 |
|
| 849 |
+
# ===== الإحصائيات =====
|
| 850 |
+
stats_display = gr.HTML()
|
| 851 |
+
|
| 852 |
# ===== المحتوى الرئيسي =====
|
| 853 |
with gr.Row(equal_height=False):
|
| 854 |
|
|
|
|
| 919 |
with gr.Column(scale=2, min_width=400):
|
| 920 |
gr.Markdown("### 💬 آراء المستخدمين")
|
| 921 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 922 |
# شريط البحث
|
| 923 |
search_input = gr.Textbox(
|
| 924 |
placeholder="🔎 بحث في الآراء...",
|
|
|
|
| 953 |
|
| 954 |
# ===== الأحداث =====
|
| 955 |
|
| 956 |
+
def update_stats():
|
| 957 |
+
return render_stats(get_stats())
|
| 958 |
+
|
| 959 |
+
def full_update(search_query="", page=1):
|
| 960 |
+
stats_html = render_stats(get_stats())
|
| 961 |
+
display = update_display(search_query, page)
|
| 962 |
+
return stats_html, display
|
| 963 |
|
| 964 |
# زر النشر
|
| 965 |
submit_btn.click(
|
| 966 |
fn=add_testimonial,
|
| 967 |
inputs=[name_input, role_input, content_input, rating_input],
|
| 968 |
outputs=[status_output, name_input, role_input, testimonials_display, char_count]
|
| 969 |
+
).then(
|
| 970 |
+
fn=update_stats,
|
| 971 |
+
outputs=[stats_display]
|
| 972 |
)
|
| 973 |
|
| 974 |
# زر التحديث
|
| 975 |
refresh_btn.click(
|
| 976 |
fn=full_update,
|
| 977 |
+
inputs=[search_input],
|
| 978 |
+
outputs=[stats_display, testimonials_display]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 979 |
)
|
| 980 |
|
| 981 |
+
# البحث
|
| 982 |
search_input.change(
|
| 983 |
fn=full_update,
|
| 984 |
+
inputs=[search_input],
|
| 985 |
+
outputs=[stats_display, testimonials_display]
|
| 986 |
)
|
| 987 |
|
| 988 |
# زر التصدير
|
|
|
|
| 1004 |
# تحميل أولي
|
| 1005 |
demo.load(
|
| 1006 |
fn=full_update,
|
| 1007 |
+
inputs=[search_input],
|
| 1008 |
+
outputs=[stats_display, testimonials_display]
|
| 1009 |
)
|
| 1010 |
|
| 1011 |
return demo
|