EngReem85 commited on
Commit
f6f5625
·
verified ·
1 Parent(s): 6f32273

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +161 -26
app.py CHANGED
@@ -71,10 +71,10 @@ CUSTOM_CSS = """
71
  100% { transform: translateX(100%) rotate(45deg); }
72
  }
73
 
74
- @keyframes glowPulse {
75
- 0% { box-shadow: 0 0 20px rgba(99, 102, 241, 0.3); }
76
- 50% { box-shadow: 0 0 40px rgba(99, 102, 241, 0.6); }
77
- 100% { box-shadow: 0 0 20px rgba(99, 102, 241, 0.3); }
78
  }
79
 
80
  /* تحسين البطاقات في الوضع الدارك */
@@ -203,6 +203,16 @@ p, span, div {
203
  background: #3d3d5c !important;
204
  border-color: #6366f1 !important;
205
  }
 
 
 
 
 
 
 
 
 
 
206
  """
207
 
208
  # ==================================================
@@ -279,7 +289,8 @@ def init_db():
279
  testimonial_id INTEGER,
280
  ip_hash TEXT,
281
  created_at TEXT,
282
- FOREIGN KEY (testimonial_id) REFERENCES testimonials(id) ON DELETE CASCADE
 
283
  )
284
  """)
285
 
@@ -489,6 +500,62 @@ def render_stats(stats):
489
  </div>
490
  """
491
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
492
  # ==================================================
493
  # إضافة رأي
494
  # ==================================================
@@ -650,7 +717,7 @@ def get_testimonials(filter_type="all", sort_by="newest", search_query="", page=
650
  return rows, total, (total + per_page - 1) // per_page if total > 0 else 1
651
 
652
  # ==================================================
653
- # عرض البطاقات
654
  # ==================================================
655
 
656
  def render_stars(rating):
@@ -659,8 +726,8 @@ def render_stars(rating):
659
  empty_stars = '☆' * (5 - int(rating))
660
  return f'<span style="font-size:1.1rem;color:#fbbf24;">{full_stars}{empty_stars}</span>'
661
 
662
- def render_card(row, show_like_button=True):
663
- """عرض بطاقة الرأي"""
664
  (id, name, role, content, rating, timestamp, likes, is_highlighted) = row
665
 
666
  highlight_style = ""
@@ -682,29 +749,38 @@ def render_card(row, show_like_button=True):
682
 
683
  role_html = f'<span style="background:#312e81;color:#818cf8;padding:0.2rem 0.6rem;border-radius:999px;font-size:0.75rem;">{escape_safe(role)}</span>' if role else ""
684
 
 
 
 
 
685
  like_button = ""
686
  if show_like_button:
687
  like_button = f"""
688
- <button onclick="likeTestimonial({id})" style="
689
- background: none;
690
- border: none;
691
- cursor: pointer;
692
- font-size: 1rem;
693
- padding: 6px 14px;
694
- border-radius: 20px;
695
- transition: all 0.2s ease;
696
- background: #2d2d44;
697
- color: #94a3b8;
698
- display: flex;
699
- align-items: center;
700
- gap: 4px;
701
- ">
702
- ❤️ {likes}
 
 
 
 
 
703
  </button>
704
  """
705
 
706
  return f"""
707
- <div class="testimonial-card" style="
708
  background:#1a1a2e;
709
  border-radius:20px;
710
  padding:1.2rem 1.5rem;
@@ -768,6 +844,56 @@ def render_card(row, show_like_button=True):
768
  {like_button}
769
  </div>
770
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
771
  """
772
 
773
  # ==================================================
@@ -795,7 +921,7 @@ def update_display(filter_type="all", sort_by="newest", search_query="", page=1)
795
 
796
  html = ""
797
  for row in rows:
798
- html += render_card(row)
799
 
800
  # أزرار التنقل
801
  if total_pages > 1:
@@ -999,7 +1125,8 @@ def create_interface():
999
  with gr.Row():
1000
  chart_display = gr.Plot(label="توزيع التقييمات")
1001
 
1002
- # ===== المحتوى الرئيسي ===== with gr.Row(equal_height=False):
 
1003
 
1004
  # ===== العمود الأيسر: إضافة رأي =====
1005
  with gr.Column(scale=1, min_width=300):
@@ -1139,6 +1266,11 @@ def create_interface():
1139
  display = update_display(filter_type, sort_by, search_query, page)
1140
  return stats_html, chart, display
1141
 
 
 
 
 
 
1142
  # زر النشر
1143
  submit_btn.click(
1144
  fn=add_testimonial,
@@ -1191,6 +1323,9 @@ def create_interface():
1191
  outputs=[name_input, role_input, content_input, status_output, char_count]
1192
  )
1193
 
 
 
 
1194
  # تحميل أولي
1195
  demo.load(
1196
  fn=full_update,
 
71
  100% { transform: translateX(100%) rotate(45deg); }
72
  }
73
 
74
+ @keyframes likeAnimation {
75
+ 0% { transform: scale(1); }
76
+ 50% { transform: scale(1.3); }
77
+ 100% { transform: scale(1); }
78
  }
79
 
80
  /* تحسين البطاقات في الوضع الدارك */
 
203
  background: #3d3d5c !important;
204
  border-color: #6366f1 !important;
205
  }
206
+
207
+ /* زر اللايك عند الضغط */
208
+ .like-btn-liked {
209
+ background: #312e81 !important;
210
+ color: #818cf8 !important;
211
+ }
212
+
213
+ .like-btn-liked:hover {
214
+ background: #4338ca !important;
215
+ }
216
  """
217
 
218
  # ==================================================
 
289
  testimonial_id INTEGER,
290
  ip_hash TEXT,
291
  created_at TEXT,
292
+ FOREIGN KEY (testimonial_id) REFERENCES testimonials(id) ON DELETE CASCADE,
293
+ UNIQUE(testimonial_id, ip_hash)
294
  )
295
  """)
296
 
 
500
  </div>
501
  """
502
 
503
+ # ==================================================
504
+ # نظام اللايك
505
+ # ==================================================
506
+
507
+ def toggle_like(testimonial_id, request: gr.Request):
508
+ """تبديل حالة اللايك (إضافة أو إزالة)"""
509
+ ip_hash = get_ip_hash(request)
510
+
511
+ conn = get_connection()
512
+ c = conn.cursor()
513
+
514
+ try:
515
+ # التحقق من وجود اللايك
516
+ c.execute(
517
+ "SELECT id FROM likes WHERE testimonial_id = ? AND ip_hash = ?",
518
+ (testimonial_id, ip_hash)
519
+ )
520
+ existing_like = c.fetchone()
521
+
522
+ if existing_like:
523
+ # إزالة اللايك
524
+ c.execute(
525
+ "DELETE FROM likes WHERE testimonial_id = ? AND ip_hash = ?",
526
+ (testimonial_id, ip_hash)
527
+ )
528
+ c.execute(
529
+ "UPDATE testimonials SET likes = likes - 1 WHERE id = ?",
530
+ (testimonial_id,)
531
+ )
532
+ liked = False
533
+ else:
534
+ # إضافة اللايك
535
+ c.execute(
536
+ "INSERT INTO likes (testimonial_id, ip_hash, created_at) VALUES (?, ?, ?)",
537
+ (testimonial_id, ip_hash, datetime.now().isoformat())
538
+ )
539
+ c.execute(
540
+ "UPDATE testimonials SET likes = likes + 1 WHERE id = ?",
541
+ (testimonial_id,)
542
+ )
543
+ liked = True
544
+
545
+ conn.commit()
546
+
547
+ # جلب العدد الجديد
548
+ c.execute("SELECT likes FROM testimonials WHERE id = ?", (testimonial_id,))
549
+ new_count = c.fetchone()[0]
550
+
551
+ return liked, new_count
552
+
553
+ except Exception as e:
554
+ logger.error(f"خطأ في اللايك: {e}")
555
+ return False, 0
556
+ finally:
557
+ conn.close()
558
+
559
  # ==================================================
560
  # إضافة رأي
561
  # ==================================================
 
717
  return rows, total, (total + per_page - 1) // per_page if total > 0 else 1
718
 
719
  # ==================================================
720
+ # عرض البطاقات مع زر لايك تفاعلي
721
  # ==================================================
722
 
723
  def render_stars(rating):
 
726
  empty_stars = '☆' * (5 - int(rating))
727
  return f'<span style="font-size:1.1rem;color:#fbbf24;">{full_stars}{empty_stars}</span>'
728
 
729
+ def render_card(row, show_like_button=True, liked=False):
730
+ """عرض بطاقة الرأي مع زر لايك تفاعلي"""
731
  (id, name, role, content, rating, timestamp, likes, is_highlighted) = row
732
 
733
  highlight_style = ""
 
749
 
750
  role_html = f'<span style="background:#312e81;color:#818cf8;padding:0.2rem 0.6rem;border-radius:999px;font-size:0.75rem;">{escape_safe(role)}</span>' if role else ""
751
 
752
+ # زر اللايك مع حالة تفاعلية
753
+ like_button_style = "background: #312e81; color: #818cf8;" if liked else "background: #2d2d44; color: #94a3b8;"
754
+ like_icon = "❤️" if liked else "🤍"
755
+
756
  like_button = ""
757
  if show_like_button:
758
  like_button = f"""
759
+ <button
760
+ onclick="likeTestimonial({id})"
761
+ class="like-btn"
762
+ data-id="{id}"
763
+ style="
764
+ background: none;
765
+ border: none;
766
+ cursor: pointer;
767
+ font-size: 1rem;
768
+ padding: 6px 14px;
769
+ border-radius: 20px;
770
+ transition: all 0.2s ease;
771
+ {like_button_style}
772
+ display: flex;
773
+ align-items: center;
774
+ gap: 6px;
775
+ font-weight: 600;
776
+ "
777
+ >
778
+ {like_icon} <span class="like-count" data-id="{id}">{likes}</span>
779
  </button>
780
  """
781
 
782
  return f"""
783
+ <div class="testimonial-card" id="card-{id}" style="
784
  background:#1a1a2e;
785
  border-radius:20px;
786
  padding:1.2rem 1.5rem;
 
844
  {like_button}
845
  </div>
846
  </div>
847
+
848
+ <script>
849
+ function likeTestimonial(id) {{
850
+ // إرسال طلب اللايك
851
+ fetch('/like/' + id, {{
852
+ method: 'POST',
853
+ headers: {{
854
+ 'Content-Type': 'application/json',
855
+ }}
856
+ }})
857
+ .then(response => response.json())
858
+ .then(data => {{
859
+ if (data.success) {{
860
+ // تحديث زر اللايك
861
+ const btn = document.querySelector(`button[data-id="${{id}}"]`);
862
+ const count = document.querySelector(`.like-count[data-id="${{id}}"]`);
863
+
864
+ if (data.liked) {{
865
+ btn.innerHTML = '❤️ <span class="like-count" data-id="${{id}}">' + data.count + '</span>';
866
+ btn.style.background = '#312e81';
867
+ btn.style.color = '#818cf8';
868
+ }} else {{
869
+ btn.innerHTML = '🤍 <span class="like-count" data-id="${{id}}">' + data.count + '</span>';
870
+ btn.style.background = '#2d2d44';
871
+ btn.style.color = '#94a3b8';
872
+ }}
873
+
874
+ // تحديث العدد
875
+ if (count) {{
876
+ count.textContent = data.count;
877
+ }}
878
+
879
+ // تحديث الإحصائيات
880
+ updateStats();
881
+ }}
882
+ }})
883
+ .catch(error => console.error('Error:', error));
884
+ }}
885
+
886
+ function updateStats() {{
887
+ // تحديث الإحصائيات عن طريق إعادة تحميل البيانات
888
+ fetch('/refresh-stats')
889
+ .then(response => response.json())
890
+ .then(data => {{
891
+ // تحديث الإحصائيات في الواجهة
892
+ document.querySelector('.stat-total').textContent = data.total;
893
+ document.querySelector('.stat-rating').textContent = data.avg_rating;
894
+ }});
895
+ }}
896
+ </script>
897
  """
898
 
899
  # ==================================================
 
921
 
922
  html = ""
923
  for row in rows:
924
+ html += render_card(row, show_like_button=True)
925
 
926
  # أزرار التنقل
927
  if total_pages > 1:
 
1125
  with gr.Row():
1126
  chart_display = gr.Plot(label="توزيع التقييمات")
1127
 
1128
+ # ===== المحتوى الرئيسي =====
1129
+ with gr.Row(equal_height=False):
1130
 
1131
  # ===== العمود الأيسر: إضافة رأي =====
1132
  with gr.Column(scale=1, min_width=300):
 
1266
  display = update_display(filter_type, sort_by, search_query, page)
1267
  return stats_html, chart, display
1268
 
1269
+ # دالة معالجة اللايك
1270
+ def handle_like(testimonial_id, request: gr.Request):
1271
+ liked, count = toggle_like(int(testimonial_id), request)
1272
+ return {"success": True, "liked": liked, "count": count}
1273
+
1274
  # زر النشر
1275
  submit_btn.click(
1276
  fn=add_testimonial,
 
1323
  outputs=[name_input, role_input, content_input, status_output, char_count]
1324
  )
1325
 
1326
+ # ===== إضافة مسار للايك =====
1327
+ demo.add_api_route("/like/{testimonial_id}", handle_like, methods=["POST"])
1328
+
1329
  # تحميل أولي
1330
  demo.load(
1331
  fn=full_update,