edu-app-ai / utils /ui_utils.py
1yahoo's picture
Upload folder using huggingface_hub
555501d verified
"""
وظائف تخصيص واجهة المستخدم (UI utilities)
"""
import streamlit as st
from typing import Dict, List, Optional
import config
def load_custom_css():
"""تحميل CSS مخصص"""
try:
with open("assets/css/style.css", "r", encoding="utf-8") as f:
st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
except:
# CSS افتراضي في حالة عدم وجود الملف
st.markdown("""
<style>
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700&display=swap');
* {
font-family: 'Tajawal', sans-serif !important;
}
.main {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background-attachment: fixed;
}
.block-container {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 2rem;
margin-top: 2rem;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
}
h1, h2, h3 {
color: #667eea;
}
.stButton > button {
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 10px;
padding: 0.75rem 2rem;
font-size: 1.1rem;
font-weight: 600;
transition: all 0.3s ease;
width: 100%;
}
.stButton > button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
}
.success-box {
background: linear-gradient(135deg, #48bb78 0%, #38a169 100%);
color: white;
padding: 1rem;
border-radius: 10px;
margin: 1rem 0;
}
.info-box {
background: linear-gradient(135deg, #4299e1 0%, #3182ce 100%);
color: white;
padding: 1rem;
border-radius: 10px;
margin: 1rem 0;
}
.warning-box {
background: linear-gradient(135deg, #ed8936 0%, #dd6b20 100%);
color: white;
padding: 1rem;
border-radius: 10px;
margin: 1rem 0;
}
.card {
background: white;
border-radius: 15px;
padding: 1.5rem;
margin: 1rem 0;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.progress-bar {
background: #e2e8f0;
border-radius: 10px;
height: 25px;
overflow: hidden;
margin: 1rem 0;
}
.progress-fill {
background: linear-gradient(90deg, #48bb78 0%, #38a169 100%);
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
transition: width 0.5s ease;
}
.badge {
display: inline-block;
padding: 0.35rem 0.75rem;
border-radius: 20px;
font-size: 0.875rem;
font-weight: 600;
margin: 0.25rem;
}
.badge-primary {
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
color: white;
}
.badge-success {
background: #48bb78;
color: white;
}
.badge-warning {
background: #ed8936;
color: white;
}
.badge-info {
background: #4299e1;
color: white;
}
</style>
""", unsafe_allow_html=True)
def show_hero_section(title: str, description: str):
"""عرض قسم Hero جميل"""
st.markdown(f"""
<div style='text-align: center; padding: 3rem 1rem;'>
<h1 style='font-size: 3em; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 0.5rem;'>
{title}
</h1>
<p style='font-size: 1.3rem; color: #4a5568; margin-top: 0;'>
{description}
</p>
</div>
""", unsafe_allow_html=True)
def show_card(title: str, content: str, icon: str = "📚"):
"""عرض بطاقة جميلة"""
st.markdown(f"""
<div class='card'>
<h3 style='margin-top: 0;'>{icon} {title}</h3>
<p style='color: #4a5568;'>{content}</p>
</div>
""", unsafe_allow_html=True)
def show_feature_cards(features: List[Dict]):
"""عرض بطاقات الميزات في أعمدة"""
cols = st.columns(len(features))
for col, feature in zip(cols, features):
with col:
st.markdown(f"""
<div class='card' style='text-align: center;'>
<div style='font-size: 3rem; margin-bottom: 0.5rem;'>{feature.get('icon', '✨')}</div>
<h3 style='margin: 0.5rem 0;'>{feature.get('title', '')}</h3>
<p style='color: #718096; font-size: 0.95rem;'>{feature.get('description', '')}</p>
</div>
""", unsafe_allow_html=True)
def show_progress_bar(percentage: float, label: str = ""):
"""عرض شريط تقدم جميل"""
st.markdown(f"""
<div class='progress-bar'>
<div class='progress-fill' style='width: {percentage}%;'>
{percentage:.1f}%
</div>
</div>
{f'<p style="text-align: center; color: #4a5568; margin-top: 0.5rem;">{label}</p>' if label else ''}
""", unsafe_allow_html=True)
def show_badge(text: str, badge_type: str = "primary"):
"""عرض شارة ملونة"""
return f"<span class='badge badge-{badge_type}'>{text}</span>"
def show_success_message(message: str):
"""رسالة نجاح جميلة"""
st.markdown(f"""
<div class='success-box'>
<strong>✅ {message}</strong>
</div>
""", unsafe_allow_html=True)
def show_info_message(message: str):
"""رسالة معلومات جميلة"""
st.markdown(f"""
<div class='info-box'>
<strong>ℹ️ {message}</strong>
</div>
""", unsafe_allow_html=True)
def show_warning_message(message: str):
"""رسالة تحذير جميلة"""
st.markdown(f"""
<div class='warning-box'>
<strong>⚠️ {message}</strong>
</div>
""", unsafe_allow_html=True)
def create_stat_card(label: str, value: str, icon: str = "📊"):
"""إنشاء بطاقة إحصائية"""
return f"""
<div class='card' style='text-align: center;'>
<div style='font-size: 2.5rem;'>{icon}</div>
<h2 style='margin: 0.5rem 0; color: #667eea;'>{value}</h2>
<p style='color: #718096; margin: 0;'>{label}</p>
</div>
"""
def show_timeline(events: List[Dict]):
"""عرض خط زمني للأحداث"""
for event in events:
status_icon = "✅" if event.get("completed") else "⏳"
st.markdown(f"""
<div class='card' style='border-right: 4px solid {"#48bb78" if event.get("completed") else "#cbd5e0"};'>
<div style='display: flex; align-items: center; gap: 1rem;'>
<div style='font-size: 2rem;'>{status_icon}</div>
<div style='flex: 1;'>
<h4 style='margin: 0; color: #2d3748;'>{event.get('title', '')}</h4>
<p style='margin: 0.25rem 0 0 0; color: #718096; font-size: 0.9rem;'>
{event.get('date', '')}
</p>
</div>
</div>
</div>
""", unsafe_allow_html=True)
def show_hub_selector(hubs: Dict) -> Optional[str]:
"""عرض محدد المحاور التعليمية"""
st.markdown("### 🎯 اختر محورك التعليمي")
selected_hub = None
cols = st.columns(2)
hub_items = list(hubs.items())
for idx, (hub_id, hub_data) in enumerate(hub_items):
col = cols[idx % 2]
with col:
if st.button(
f"{hub_data['icon']} {hub_data['name']}\n\n{hub_data['description']}",
key=f"hub_{hub_id}",
use_container_width=True
):
selected_hub = hub_id
return selected_hub
def init_session_state(defaults: Dict):
"""تهيئة session state بقيم افتراضية"""
for key, value in defaults.items():
if key not in st.session_state:
st.session_state[key] = value
def show_lesson_card(lesson: Dict, lesson_id: str, is_completed: bool = False):
"""عرض بطاقة درس"""
status_color = "#48bb78" if is_completed else "#cbd5e0"
status_text = "مكتمل ✅" if is_completed else "قيد الانتظار ⏳"
st.markdown(f"""
<div class='card' style='border-right: 4px solid {status_color};'>
<div style='display: flex; justify-content: space-between; align-items: start;'>
<div style='flex: 1;'>
<h4 style='margin: 0 0 0.5rem 0; color: #2d3748;'>{lesson.get('title', '')}</h4>
<p style='color: #718096; margin: 0.25rem 0;'>{lesson.get('description', '')}</p>
<div style='margin-top: 0.75rem;'>
<span class='badge badge-info'>⏱️ {lesson.get('duration_mins', 0)} دقيقة</span>
<span class='badge badge-primary'>{lesson.get('type', 'درس')}</span>
</div>
</div>
<div style='text-align: left; min-width: 100px;'>
<span class='badge badge-{"success" if is_completed else "warning"}'>{status_text}</span>
</div>
</div>
</div>
""", unsafe_allow_html=True)