|
import streamlit as st |
|
import pandas as pd |
|
import numpy as np |
|
import matplotlib.pyplot as plt |
|
import plotly.express as px |
|
import plotly.graph_objects as go |
|
|
|
|
|
|
|
|
|
st.title("عرض تحسينات واجهة المستخدم") |
|
|
|
|
|
@st.cache_data |
|
def get_sample_data(): |
|
items = pd.DataFrame({ |
|
'رقم البند': ['UB1', 'UB2', 'UB3', 'UB4', 'UB5'], |
|
'وصف البند': ['حفر أساسات', 'صب خرسانة مسلحة', 'أعمال طوب', 'أعمال تشطيبات', 'أعمال كهرباء'], |
|
'الوحدة': ['م3', 'م3', 'م2', 'م2', 'نقطة'], |
|
'الكمية': [350.0, 120.0, 500.0, 800.0, 150.0], |
|
'سعر الوحدة': [80.0, 950.0, 45.0, 120.0, 90.0], |
|
'الإجمالي': [28000.0, 114000.0, 22500.0, 96000.0, 13500.0], |
|
'إستراتيجية التسعير': ['نقص', 'زيادة', 'متوازن', 'زيادة', 'نقص'] |
|
}) |
|
return items |
|
|
|
items = get_sample_data() |
|
|
|
|
|
st.markdown("<h3 style='color: #1F7A8C; background: linear-gradient(to right, #f0f9ff, #ffffff); padding: 10px; border-radius: 5px;'>بنود التسعير غير المتوازن</h3>", unsafe_allow_html=True) |
|
|
|
|
|
def highlight_row(row): |
|
strategy = row['إستراتيجية التسعير'] |
|
styles = [''] * len(row) |
|
|
|
|
|
if strategy == 'زيادة': |
|
background = 'linear-gradient(90deg, rgba(168, 230, 207, 0.3), rgba(168, 230, 207, 0.1))' |
|
text_color = '#1F7A8C' |
|
elif strategy == 'نقص': |
|
background = 'linear-gradient(90deg, rgba(255, 154, 162, 0.3), rgba(255, 154, 162, 0.1))' |
|
text_color = '#9D2A45' |
|
else: |
|
background = 'linear-gradient(90deg, rgba(220, 237, 255, 0.3), rgba(220, 237, 255, 0.1))' |
|
text_color = '#555555' |
|
|
|
|
|
for i in range(len(styles)): |
|
styles[i] = f'background: {background}; color: {text_color}; border-bottom: 1px solid #ddd;' |
|
|
|
|
|
if strategy == 'زيادة': |
|
styles[list(row.index).index('إستراتيجية التسعير')] = 'background-color: #a8e6cf; color: #007263; font-weight: bold; border-radius: 5px; text-align: center;' |
|
elif strategy == 'نقص': |
|
styles[list(row.index).index('إستراتيجية التسعير')] = 'background-color: #ff9aa2; color: #9D2A45; font-weight: bold; border-radius: 5px; text-align: center;' |
|
else: |
|
styles[list(row.index).index('إستراتيجية التسعير')] = 'background-color: #dceeff; color: #555555; font-weight: bold; border-radius: 5px; text-align: center;' |
|
|
|
|
|
price_idx = list(row.index).index('سعر الوحدة') |
|
styles[price_idx] = styles[price_idx] + 'font-weight: bold;' |
|
|
|
|
|
total_idx = list(row.index).index('الإجمالي') |
|
styles[total_idx] = styles[total_idx] + 'font-weight: bold;' |
|
|
|
return styles |
|
|
|
|
|
styled_items = items.style.apply(highlight_row, axis=1) |
|
|
|
|
|
styled_items = styled_items.format({ |
|
'الكمية': '{:,.2f}', |
|
'سعر الوحدة': '{:,.2f}', |
|
'الإجمالي': '{:,.2f}' |
|
}) |
|
|
|
st.dataframe(styled_items, use_container_width=True, height=None) |
|
|
|
|
|
st.markdown("<h3 style='color: #1F7A8C; background: linear-gradient(to right, #f0f9ff, #ffffff); padding: 10px; border-radius: 5px;'>مقارنة التسعير المتوازن وغير المتوازن</h3>", unsafe_allow_html=True) |
|
|
|
|
|
original_items = items.copy() |
|
original_items['سعر الوحدة'] = [70.0, 820.0, 45.0, 100.0, 110.0] |
|
original_items['الإجمالي'] = original_items['الكمية'] * original_items['سعر الوحدة'] |
|
|
|
original_total = original_items['الإجمالي'].sum() |
|
unbalanced_total = items['الإجمالي'].sum() |
|
|
|
|
|
st.markdown(""" |
|
<style> |
|
.metric-container { |
|
background: linear-gradient(to right, #f1f8ff, #ffffff); |
|
border-radius: 10px; |
|
padding: 15px; |
|
box-shadow: 0 2px 5px rgba(0,0,0,0.05); |
|
text-align: center; |
|
border: 1px solid #e6f2ff; |
|
} |
|
.metric-title { |
|
color: #555; |
|
font-size: 0.9em; |
|
margin-bottom: 5px; |
|
} |
|
.metric-value { |
|
color: #1F7A8C; |
|
font-size: 1.8em; |
|
font-weight: bold; |
|
margin: 5px 0; |
|
} |
|
.metric-delta { |
|
font-size: 0.9em; |
|
font-weight: bold; |
|
padding: 3px 8px; |
|
border-radius: 10px; |
|
display: inline-block; |
|
margin-top: 5px; |
|
} |
|
.positive-delta { |
|
background-color: rgba(40, 167, 69, 0.1); |
|
color: #28a745; |
|
} |
|
.negative-delta { |
|
background-color: rgba(220, 53, 69, 0.1); |
|
color: #dc3545; |
|
} |
|
.neutral-delta { |
|
background-color: rgba(108, 117, 125, 0.1); |
|
color: #6c757d; |
|
} |
|
</style> |
|
""", unsafe_allow_html=True) |
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
st.markdown(""" |
|
<div class="metric-container"> |
|
<div class="metric-title">إجمالي التسعير المتوازن</div> |
|
<div class="metric-value">{:,.2f} ريال</div> |
|
<div class="metric-delta neutral-delta">التسعير الأصلي</div> |
|
</div> |
|
""".format(original_total), unsafe_allow_html=True) |
|
|
|
with col2: |
|
st.markdown(""" |
|
<div class="metric-container"> |
|
<div class="metric-title">إجمالي التسعير غير المتوازن</div> |
|
<div class="metric-value">{:,.2f} ريال</div> |
|
<div class="metric-delta {}">بعد إعادة توزيع الأسعار</div> |
|
</div> |
|
""".format( |
|
unbalanced_total, |
|
"positive-delta" if unbalanced_total > original_total else "negative-delta" if unbalanced_total < original_total else "neutral-delta" |
|
), unsafe_allow_html=True) |
|
|
|
with col3: |
|
diff = unbalanced_total - original_total |
|
delta_percent = diff/original_total*100 if original_total > 0 else 0 |
|
|
|
st.markdown(""" |
|
<div class="metric-container"> |
|
<div class="metric-title">الفرق بين التسعيرين</div> |
|
<div class="metric-value">{:,.2f} ريال</div> |
|
<div class="metric-delta {}">نسبة الفرق: {:+.1f}%</div> |
|
</div> |
|
""".format( |
|
diff, |
|
"positive-delta" if diff > 0 else "negative-delta" if diff < 0 else "neutral-delta", |
|
delta_percent |
|
), unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("<h3 style='color: #1F7A8C; background: linear-gradient(to right, #f0f9ff, #ffffff); padding: 10px; border-radius: 5px;'>تحليل بصري للتسعير غير المتوازن</h3>", unsafe_allow_html=True) |
|
|
|
|
|
chart_data = pd.DataFrame({ |
|
'وصف البند': original_items['وصف البند'], |
|
'التسعير المتوازن': original_items['الإجمالي'], |
|
'التسعير غير المتوازن': items['الإجمالي'] |
|
}) |
|
|
|
|
|
chart_data['نسبة التغيير'] = (chart_data['التسعير غير المتوازن'] - chart_data['التسعير المتوازن']) / chart_data['التسعير المتوازن'] * 100 |
|
|
|
|
|
bar_colors = [] |
|
for change in chart_data['نسبة التغيير']: |
|
if change > 5: |
|
bar_colors.append('#1F7A8C') |
|
elif change > 0: |
|
bar_colors.append('#81B29A') |
|
elif change > -5: |
|
bar_colors.append('#F2CC8F') |
|
else: |
|
bar_colors.append('#E07A5F') |
|
|
|
|
|
chart_tabs = st.tabs(["مخطط شريطي", "مخطط مقارنة", "مخطط نسبة التغيير"]) |
|
|
|
with chart_tabs[0]: |
|
|
|
fig = go.Figure() |
|
|
|
fig.add_trace(go.Bar( |
|
x=chart_data['وصف البند'], |
|
y=chart_data['التسعير المتوازن'], |
|
name='التسعير المتوازن', |
|
marker_color='rgba(55, 83, 109, 0.7)' |
|
)) |
|
|
|
fig.add_trace(go.Bar( |
|
x=chart_data['وصف البند'], |
|
y=chart_data['التسعير غير المتوازن'], |
|
name='التسعير غير المتوازن', |
|
marker_color=bar_colors |
|
)) |
|
|
|
fig.update_layout( |
|
title='مقارنة بين التسعير المتوازن وغير المتوازن', |
|
xaxis_tickfont_size=14, |
|
yaxis=dict( |
|
title='الإجمالي (ريال)', |
|
titlefont_size=16, |
|
tickfont_size=14, |
|
), |
|
legend=dict( |
|
x=0.01, |
|
y=0.99, |
|
bgcolor='rgba(255, 255, 255, 0.8)', |
|
bordercolor='rgba(0, 0, 0, 0.1)', |
|
borderwidth=1 |
|
), |
|
barmode='group', |
|
bargap=0.15, |
|
bargroupgap=0.1, |
|
plot_bgcolor='rgba(240, 249, 255, 0.5)', |
|
margin=dict(t=50, b=50, l=20, r=20) |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
with chart_tabs[1]: |
|
|
|
fig = go.Figure() |
|
|
|
|
|
fig.add_trace(go.Scatter( |
|
x=chart_data['وصف البند'], |
|
y=chart_data['التسعير المتوازن'], |
|
name='التسعير المتوازن', |
|
mode='lines+markers', |
|
line=dict(color='rgb(55, 83, 109)', width=3), |
|
marker=dict(size=10, color='rgb(55, 83, 109)') |
|
)) |
|
|
|
|
|
fig.add_trace(go.Scatter( |
|
x=chart_data['وصف البند'], |
|
y=chart_data['التسعير غير المتوازن'], |
|
name='التسعير غير المتوازن', |
|
mode='lines+markers', |
|
line=dict(color='rgb(26, 118, 255)', width=3), |
|
marker=dict( |
|
size=12, |
|
color=bar_colors, |
|
line=dict(width=2, color='white') |
|
) |
|
)) |
|
|
|
|
|
fig.update_layout( |
|
title='مقارنة مرئية بين استراتيجيات التسعير', |
|
xaxis_tickfont_size=14, |
|
yaxis=dict( |
|
title='القيمة الإجمالية (ريال)', |
|
titlefont_size=16, |
|
tickfont_size=14, |
|
gridcolor='rgba(200, 200, 200, 0.2)' |
|
), |
|
legend=dict( |
|
x=0.01, |
|
y=0.99, |
|
bgcolor='rgba(255, 255, 255, 0.8)', |
|
bordercolor='rgba(0, 0, 0, 0.1)', |
|
borderwidth=1 |
|
), |
|
plot_bgcolor='rgba(240, 249, 255, 0.5)', |
|
margin=dict(t=50, b=50, l=20, r=20) |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
with chart_tabs[2]: |
|
|
|
fig = go.Figure() |
|
|
|
|
|
fig.add_trace(go.Bar( |
|
x=chart_data['وصف البند'], |
|
y=chart_data['نسبة التغيير'], |
|
name='نسبة التغيير', |
|
marker_color=bar_colors, |
|
text=[f"{val:.1f}%" for val in chart_data['نسبة التغيير']], |
|
textposition='auto' |
|
)) |
|
|
|
|
|
fig.add_shape( |
|
type="line", |
|
x0=-0.5, |
|
y0=0, |
|
x1=len(chart_data['وصف البند'])-0.5, |
|
y1=0, |
|
line=dict( |
|
color="black", |
|
width=2, |
|
dash="dash", |
|
) |
|
) |
|
|
|
|
|
fig.update_layout( |
|
title='نسبة التغيير في أسعار البنود (%)', |
|
xaxis_tickfont_size=14, |
|
yaxis=dict( |
|
title='نسبة التغيير (%)', |
|
titlefont_size=16, |
|
tickfont_size=14, |
|
gridcolor='rgba(200, 200, 200, 0.2)', |
|
zeroline=True, |
|
zerolinecolor='black', |
|
zerolinewidth=2 |
|
), |
|
plot_bgcolor='rgba(240, 249, 255, 0.5)', |
|
margin=dict(t=50, b=50, l=20, r=20) |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### جدول مفصل بنسب التغيير") |
|
|
|
|
|
table_data = chart_data[['وصف البند', 'التسعير المتوازن', 'التسعير غير المتوازن', 'نسبة التغيير']] |
|
|
|
|
|
def highlight_change(row): |
|
change = row['نسبة التغيير'] |
|
if change > 5: |
|
return ['', '', '', 'background-color: rgba(31, 122, 140, 0.3); color: #1F7A8C; font-weight: bold;'] |
|
elif change > 0: |
|
return ['', '', '', 'background-color: rgba(129, 178, 154, 0.3); color: #2A9D8F; font-weight: bold;'] |
|
elif change > -5: |
|
return ['', '', '', 'background-color: rgba(242, 204, 143, 0.3); color: #BC6C25; font-weight: bold;'] |
|
else: |
|
return ['', '', '', 'background-color: rgba(224, 122, 95, 0.3); color: #AE2012; font-weight: bold;'] |
|
|
|
|
|
styled_table = table_data.style.apply(highlight_change, axis=1).format({ |
|
'التسعير المتوازن': '{:,.2f} ريال', |
|
'التسعير غير المتوازن': '{:,.2f} ريال', |
|
'نسبة التغيير': '{:+.1f}%' |
|
}) |
|
|
|
st.dataframe(styled_table, use_container_width=True) |
|
|
|
|
|
st.markdown("<hr style='margin-top: 30px; margin-bottom: 20px; border-top: 1px solid #ddd;'>", unsafe_allow_html=True) |
|
st.markdown("<h3 style='color: #1F7A8C; background: linear-gradient(to right, #f0f9ff, #ffffff); padding: 10px; border-radius: 5px;'>حفظ وتصدير البيانات</h3>", unsafe_allow_html=True) |
|
|
|
st.markdown(""" |
|
<style> |
|
.action-card { |
|
background: linear-gradient(135deg, #f8f9fa, #e9ecef); |
|
border-radius: 10px; |
|
padding: 20px; |
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1); |
|
border-left: 5px solid #1F7A8C; |
|
transition: all 0.3s ease; |
|
} |
|
.action-card:hover { |
|
box-shadow: 0 5px 15px rgba(0,0,0,0.1); |
|
transform: translateY(-2px); |
|
} |
|
.action-icon { |
|
color: #1F7A8C; |
|
font-size: 24px; |
|
margin-bottom: 10px; |
|
} |
|
.action-title { |
|
color: #333; |
|
font-size: 18px; |
|
font-weight: bold; |
|
margin-bottom: 10px; |
|
} |
|
.action-desc { |
|
color: #666; |
|
font-size: 14px; |
|
margin-bottom: 15px; |
|
} |
|
</style> |
|
""", unsafe_allow_html=True) |
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
|
|
st.markdown(""" |
|
<div class="action-card"> |
|
<div class="action-icon">💾</div> |
|
<div class="action-title">حفظ التسعير غير المتوازن</div> |
|
<div class="action-desc">قم بحفظ التسعير الحالي في المشروع لاستخدامه لاحقاً في التقارير وفي إجمالي التسعير.</div> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
if st.button("حفظ التسعير غير المتوازن", type="primary", use_container_width=True): |
|
st.success("تم حفظ التسعير غير المتوازن بنجاح!") |
|
st.balloons() |
|
|
|
with col2: |
|
|
|
st.markdown(""" |
|
<div class="action-card"> |
|
<div class="action-icon">📊</div> |
|
<div class="action-title">تصدير البيانات</div> |
|
<div class="action-desc">قم بتصدير جدول التسعير الحالي بصيغة CSV لاستخدامه في برامج أخرى مثل Excel.</div> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
export_button = st.button("تجهيز ملف للتصدير", use_container_width=True) |
|
if export_button: |
|
|
|
csv = items.to_csv(index=False) |
|
st.success("تم تجهيز ملف التصدير بنجاح! يمكنك تنزيله الآن.") |
|
|
|
st.download_button( |
|
label="تنزيل ملف CSV", |
|
data=csv, |
|
file_name="unbalanced_pricing.csv", |
|
mime="text/csv", |
|
use_container_width=True |
|
) |