| 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 |
| ) |