Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import streamlit as st
|
| 2 |
import folium
|
| 3 |
from streamlit_folium import folium_static
|
|
|
|
| 4 |
from datetime import datetime
|
| 5 |
import gspread
|
| 6 |
from google.oauth2.service_account import Credentials
|
|
@@ -673,6 +674,34 @@ h3 {
|
|
| 673 |
""", unsafe_allow_html=True)
|
| 674 |
|
| 675 |
# ========== توابع اصلی ==========
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 676 |
def create_ride_map():
|
| 677 |
"""ایجاد نقشه سفر با Folium - نسخه اصلاح شده با مناطق عمومی"""
|
| 678 |
# نقاط تقریبی برای مناطق عمومی جنوب و غرب تهران
|
|
@@ -774,62 +803,7 @@ def create_likert_question(question, key, scale_type="5point"):
|
|
| 774 |
return value
|
| 775 |
|
| 776 |
# ========== توابع مدیریت دادهها ==========
|
| 777 |
-
def apply_global_styles():
|
| 778 |
-
st.markdown("""
|
| 779 |
-
<style>
|
| 780 |
-
/* ===== بازنویسی کامل استایلهای پیشفرض ===== */
|
| 781 |
-
|
| 782 |
-
/* پایهترین سطوح */
|
| 783 |
-
html, body, #root, .stApp {
|
| 784 |
-
direction: rtl !important;
|
| 785 |
-
text-align: right !important;
|
| 786 |
-
font-family: 'B Nazanin' !important;
|
| 787 |
-
overflow-x: hidden !important;
|
| 788 |
-
}
|
| 789 |
-
|
| 790 |
-
/* دکمهها - بالاترین سطح Specificity */
|
| 791 |
-
body .stButton>button,
|
| 792 |
-
body .stButton>button:hover,
|
| 793 |
-
body .stButton>button:focus {
|
| 794 |
-
margin-right: auto !important;
|
| 795 |
-
margin-left: 0 !important;
|
| 796 |
-
float: right !important;
|
| 797 |
-
transform: none !important;
|
| 798 |
-
}
|
| 799 |
-
|
| 800 |
-
/* اسلایدرها - بازنویسی کامل */
|
| 801 |
-
body div[data-testid="stSlider"] > div:first-child,
|
| 802 |
-
body div[data-testid="stSlider"] > div:first-child > div {
|
| 803 |
-
direction: ltr !important;
|
| 804 |
-
background:
|
| 805 |
-
repeating-linear-gradient(
|
| 806 |
-
to right,
|
| 807 |
-
#6a0dad, #6a0dad 3px,
|
| 808 |
-
transparent 3px, transparent calc(20% - 2px),
|
| 809 |
-
#6a0dad calc(20%), #6a0dad calc(20% + 2px),
|
| 810 |
-
transparent calc(20% + 2px), transparent calc(40% - 2px),
|
| 811 |
-
#6a0dad calc(40%), #6a0dad calc(40% + 2px),
|
| 812 |
-
transparent calc(40% + 2px), transparent calc(60% - 2px),
|
| 813 |
-
#6a0dad calc(60%), #6a0dad calc(60% + 2px),
|
| 814 |
-
transparent calc(60% + 2px), transparent calc(80% - 2px),
|
| 815 |
-
#6a0dad calc(80%), #6a0dad calc(80% + 2px)
|
| 816 |
-
),
|
| 817 |
-
linear-gradient(to right, #E6E6FA, #6A0DAD) !important;
|
| 818 |
-
}
|
| 819 |
-
|
| 820 |
-
/* بازنویسی کامل بلوکها */
|
| 821 |
-
body .stHorizontalBlock > div,
|
| 822 |
-
body .stVerticalBlock > div {
|
| 823 |
-
padding-right: 12px !important;
|
| 824 |
-
padding-left: 0 !important;
|
| 825 |
-
}
|
| 826 |
|
| 827 |
-
/* مخفی کردن اسکرول افقی */
|
| 828 |
-
body ::-webkit-scrollbar {
|
| 829 |
-
display: none !important;
|
| 830 |
-
}
|
| 831 |
-
</style>
|
| 832 |
-
""", unsafe_allow_html=True)
|
| 833 |
def get_credentials():
|
| 834 |
"""دریافت اعتبارنامه از Secrets"""
|
| 835 |
try:
|
|
@@ -944,23 +918,6 @@ def create_likert_question(question, key, scale_type="5point"):
|
|
| 944 |
""",
|
| 945 |
unsafe_allow_html=True
|
| 946 |
)
|
| 947 |
-
# اضافه کردن JavaScript مخصوص این اسلایدر
|
| 948 |
-
st.components.v1.html(f"""
|
| 949 |
-
<script>
|
| 950 |
-
(function() {{
|
| 951 |
-
const slider = parent.document.querySelector('[data-testid="stSlider"] [aria-valuenow="{value}"]');
|
| 952 |
-
if(slider) {{
|
| 953 |
-
slider.parentElement.style.background = `repeating-linear-gradient(
|
| 954 |
-
to right,
|
| 955 |
-
#6a0dad, #6a0dad 2px,
|
| 956 |
-
transparent 2px, transparent calc(100%/{max_value-1} - 1px),
|
| 957 |
-
#6a0dad calc(100%/{max_value-1}), #6a0dad calc(100%/{max_value-1} + 1px)
|
| 958 |
-
)`;
|
| 959 |
-
}}
|
| 960 |
-
}})();
|
| 961 |
-
</script>
|
| 962 |
-
""", height=0)
|
| 963 |
-
|
| 964 |
return value
|
| 965 |
|
| 966 |
# ========== بخشهای فرم ==========
|
|
@@ -1138,43 +1095,43 @@ def attention_check1():
|
|
| 1138 |
st.warning("لطفاً یک گزینه را انتخاب کنید")
|
| 1139 |
|
| 1140 |
def random_likert_questions():
|
| 1141 |
-
"""نمایش سوالات لیکرت به
|
| 1142 |
-
# تعریف گروههای سوالات با ترتیب
|
| 1143 |
question_groups = [
|
| 1144 |
{
|
| 1145 |
"title": "عدالت توزیعی",
|
| 1146 |
"key": "distributive",
|
| 1147 |
"questions": [
|
| 1148 |
-
{"key": "distributive_1", "question": "قیمتی که به شما ارائه شد، منصفانه است.", "scale":
|
| 1149 |
-
{"key": "distributive_2", "question": "قیمتی که به شما ارائه شد، معقول است.", "scale":
|
| 1150 |
-
{"key": "distributive_3", "question": "قیمتی که به شما ارائه شد، قابل قبول است.", "scale":
|
| 1151 |
]
|
| 1152 |
},
|
| 1153 |
{
|
| 1154 |
"title": "سوال توجه",
|
| 1155 |
"key": "attention_check",
|
| 1156 |
"questions": [
|
| 1157 |
-
{"key": "attention_check2", "question": "لطفاً برای اطمینان از دقت پاسخهایتان، گزینه 'کاملاً موافقم' را انتخاب کنید.", "scale":
|
| 1158 |
]
|
| 1159 |
},
|
| 1160 |
{
|
| 1161 |
"title": "عدالت رویهای",
|
| 1162 |
"key": "procedural",
|
| 1163 |
"questions": [
|
| 1164 |
-
{"key": "procedural_1", "question": "فرآیند و رویه قیمتگذاری پلتفرم قابل قبول است.", "scale":
|
| 1165 |
-
{"key": "procedural_2", "question": "فرآیند و رویه قیمتگذاری پلتفرم منصفانه است.", "scale":
|
| 1166 |
-
{"key": "procedural_3", "question": "فرآیند و رویه قیمتگذاری پلتفرم معقول است.", "scale":
|
| 1167 |
]
|
| 1168 |
},
|
| 1169 |
{
|
| 1170 |
"title": "عدالت اطلاعاتی",
|
| 1171 |
"key": "informational",
|
| 1172 |
"questions": [
|
| 1173 |
-
{"key": "informational_1", "question": "پلتفرم به صورت صادقانه دلایل تعیین قیمت را توضیح داد.", "scale":
|
| 1174 |
-
{"key": "informational_2", "question": "پلتفرم به طور کامل عوامل مؤثر بر تعیین قیمت را شرح داد.", "scale":
|
| 1175 |
-
{"key": "informational_3", "question": "دلایل ارائهشده برای تعیین قیمت منطقی و قابل قبول بود.", "scale":
|
| 1176 |
-
{"key": "informational_4", "question": "توضیحات درباره تعیین قیمت بلافاصله و در زمان مناسب نمایش داده شد.", "scale":
|
| 1177 |
-
{"key": "informational_5", "question": "توضیحات پلتفرم درباره تعیین قیمت، متناسب با شرایط سفر من بود.", "scale":
|
| 1178 |
]
|
| 1179 |
}
|
| 1180 |
]
|
|
@@ -1183,36 +1140,31 @@ def random_likert_questions():
|
|
| 1183 |
if 'current_likert_group' not in st.session_state:
|
| 1184 |
st.session_state.current_likert_group = 0
|
| 1185 |
st.session_state.answers = st.session_state.get('answers', {})
|
| 1186 |
-
|
| 1187 |
# دریافت گروه جاری
|
| 1188 |
current_group = question_groups[st.session_state.current_likert_group]
|
| 1189 |
-
|
| 1190 |
# نمایش عنوان گروه
|
| 1191 |
st.markdown(f"## {current_group['title']}")
|
| 1192 |
st.markdown("---")
|
| 1193 |
-
|
| 1194 |
-
#
|
| 1195 |
-
|
| 1196 |
for question in current_group['questions']:
|
| 1197 |
-
|
| 1198 |
-
question["question"],
|
| 1199 |
-
question["key"],
|
| 1200 |
-
question["scale"]
|
| 1201 |
)
|
| 1202 |
-
|
| 1203 |
-
|
| 1204 |
-
|
| 1205 |
-
|
| 1206 |
-
|
| 1207 |
-
# دکمه ادامه
|
| 1208 |
-
button_label = "ادامه به گروه بعدی" if st.session_state.current_likert_group < len(question_groups)-1 else "اتمام پرسشنامه"
|
| 1209 |
|
| 1210 |
if st.button(button_label, disabled=not all_answered):
|
| 1211 |
-
# ذخیره پاسخهای این گروه
|
| 1212 |
-
for key, value in answers.items():
|
| 1213 |
-
st.session_state.answers[key] = value
|
| 1214 |
-
|
| 1215 |
-
# رفتن به گروه بعدی یا صفحه پایانی
|
| 1216 |
if st.session_state.current_likert_group < len(question_groups) - 1:
|
| 1217 |
st.session_state.current_likert_group += 1
|
| 1218 |
st.rerun()
|
|
@@ -1221,8 +1173,8 @@ def random_likert_questions():
|
|
| 1221 |
st.rerun()
|
| 1222 |
|
| 1223 |
if not all_answered:
|
| 1224 |
-
st.warning("لطفاً به تمام سوالات این بخش پاسخ دهید")
|
| 1225 |
-
|
| 1226 |
def explanation_questions():
|
| 1227 |
"""سوالات درباره توضیحات قیمت"""
|
| 1228 |
st.markdown("""
|
|
@@ -1423,14 +1375,11 @@ def thank_you_page():
|
|
| 1423 |
|
| 1424 |
# ========== مدیریت وضعیت و صفحهبندی ==========
|
| 1425 |
def main():
|
| 1426 |
-
|
| 1427 |
-
apply_global_styles()
|
| 1428 |
# تشخیص دستگاه
|
| 1429 |
user_agent = st.query_params.get("user_agent", [""])[0]
|
| 1430 |
st.session_state.is_desktop = "mobile" not in user_agent.lower()
|
| 1431 |
|
| 1432 |
|
| 1433 |
-
# اینجا میتوانید از is_desktop برای تعیین اینکه نمایشگر موبایل اعمال شود، استفاده کنید
|
| 1434 |
if st.session_state.is_desktop:
|
| 1435 |
# اطمینان از نمایش همان حالت موبایل برای همه دستگاهها
|
| 1436 |
st.session_state.is_desktop = False
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
import folium
|
| 3 |
from streamlit_folium import folium_static
|
| 4 |
+
import streamlit.components.v1 as components
|
| 5 |
from datetime import datetime
|
| 6 |
import gspread
|
| 7 |
from google.oauth2.service_account import Credentials
|
|
|
|
| 674 |
""", unsafe_allow_html=True)
|
| 675 |
|
| 676 |
# ========== توابع اصلی ==========
|
| 677 |
+
def custom_likert_slider(question, key, points=7):
|
| 678 |
+
options = {
|
| 679 |
+
5: ["کاملاً مخالفم", "مخالفم", "خنثی", "موافقم", "کاملاً موافقم"],
|
| 680 |
+
7: ["کاملاً مخالفم", "نسبتاً مخالفم", "کمی مخالفم", "خنثی", "کمی موافقم", "نسبتاً موافقم", "کاملاً موافقم"]
|
| 681 |
+
}
|
| 682 |
+
labels = options.get(points)
|
| 683 |
+
slider_id = f"slider_{key}"
|
| 684 |
+
|
| 685 |
+
# ساخت HTML برای اسلایدر + ارسال مقدار با postMessage
|
| 686 |
+
html = f"""
|
| 687 |
+
<div id="container_{key}" style="direction: rtl; font-family: 'B Nazanin'; margin-bottom: 30px;">
|
| 688 |
+
<label style="font-size: 16px; font-weight: bold;">{question}</label>
|
| 689 |
+
<div style="display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 5px;">
|
| 690 |
+
{''.join(f"<span>{label}</span>" for label in labels)}
|
| 691 |
+
</div>
|
| 692 |
+
<input type="range" id="{slider_id}" min="1" max="{points}" step="1" value="{(points+1)//2}"
|
| 693 |
+
style="width: 100%; accent-color: #6a0dad;"
|
| 694 |
+
oninput="document.getElementById('output_{key}').innerText=this.value;
|
| 695 |
+
window.parent.postMessage({{type: 'streamlit:setComponentValue', value: parseInt(this.value)}}, '*')">
|
| 696 |
+
<p style="text-align: center; margin-top: 10px;">پاسخ انتخابشده: <strong><span id="output_{key}">{(points+1)//2}</span></strong></p>
|
| 697 |
+
</div>
|
| 698 |
+
"""
|
| 699 |
+
|
| 700 |
+
# مقدار برگشتی (از JS)
|
| 701 |
+
selected_value = components.html(html, height=150, key=f"component_{key}")
|
| 702 |
+
|
| 703 |
+
return selected_value
|
| 704 |
+
|
| 705 |
def create_ride_map():
|
| 706 |
"""ایجاد نقشه سفر با Folium - نسخه اصلاح شده با مناطق عمومی"""
|
| 707 |
# نقاط تقریبی برای مناطق عمومی جنوب و غرب تهران
|
|
|
|
| 803 |
return value
|
| 804 |
|
| 805 |
# ========== توابع مدیریت دادهها ==========
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 806 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 807 |
def get_credentials():
|
| 808 |
"""دریافت اعتبارنامه از Secrets"""
|
| 809 |
try:
|
|
|
|
| 918 |
""",
|
| 919 |
unsafe_allow_html=True
|
| 920 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 921 |
return value
|
| 922 |
|
| 923 |
# ========== بخشهای فرم ==========
|
|
|
|
| 1095 |
st.warning("لطفاً یک گزینه را انتخاب کنید")
|
| 1096 |
|
| 1097 |
def random_likert_questions():
|
| 1098 |
+
"""نمایش سوالات لیکرت با اسلایدرهای سفارشی قابل ذخیرهسازی"""
|
| 1099 |
+
# تعریف گروههای سوالات با ترتیب مشخص
|
| 1100 |
question_groups = [
|
| 1101 |
{
|
| 1102 |
"title": "عدالت توزیعی",
|
| 1103 |
"key": "distributive",
|
| 1104 |
"questions": [
|
| 1105 |
+
{"key": "distributive_1", "question": "قیمتی که به شما ارائه شد، منصفانه است.", "scale": 7},
|
| 1106 |
+
{"key": "distributive_2", "question": "قیمتی که به شما ارائه شد، معقول است.", "scale": 7},
|
| 1107 |
+
{"key": "distributive_3", "question": "قیمتی که به شما ارائه شد، قابل قبول است.", "scale": 7}
|
| 1108 |
]
|
| 1109 |
},
|
| 1110 |
{
|
| 1111 |
"title": "سوال توجه",
|
| 1112 |
"key": "attention_check",
|
| 1113 |
"questions": [
|
| 1114 |
+
{"key": "attention_check2", "question": "لطفاً برای اطمینان از دقت پاسخهایتان، گزینه 'کاملاً موافقم' را انتخاب کنید.", "scale": 7}
|
| 1115 |
]
|
| 1116 |
},
|
| 1117 |
{
|
| 1118 |
"title": "عدالت رویهای",
|
| 1119 |
"key": "procedural",
|
| 1120 |
"questions": [
|
| 1121 |
+
{"key": "procedural_1", "question": "فرآیند و رویه قیمتگذاری پلتفرم قابل قبول است.", "scale": 7},
|
| 1122 |
+
{"key": "procedural_2", "question": "فرآیند و رویه قیمتگذاری پلتفرم منصفانه است.", "scale": 7},
|
| 1123 |
+
{"key": "procedural_3", "question": "فرآیند و رویه قیمتگذاری پلتفرم معقول است.", "scale": 7}
|
| 1124 |
]
|
| 1125 |
},
|
| 1126 |
{
|
| 1127 |
"title": "عدالت اطلاعاتی",
|
| 1128 |
"key": "informational",
|
| 1129 |
"questions": [
|
| 1130 |
+
{"key": "informational_1", "question": "پلتفرم به صورت صادقانه دلایل تعیین قیمت را توضیح داد.", "scale": 5},
|
| 1131 |
+
{"key": "informational_2", "question": "پلتفرم به طور کامل عوامل مؤثر بر تعیین قیمت را شرح داد.", "scale": 5},
|
| 1132 |
+
{"key": "informational_3", "question": "دلایل ارائهشده برای تعیین قیمت منطقی و قابل قبول بود.", "scale": 5},
|
| 1133 |
+
{"key": "informational_4", "question": "توضیحات درباره تعیین قیمت بلافاصله و در زمان مناسب نمایش داده شد.", "scale": 5},
|
| 1134 |
+
{"key": "informational_5", "question": "توضیحات پلتفرم درباره تعیین قیمت، متناسب با شرایط سفر من بود.", "scale": 5}
|
| 1135 |
]
|
| 1136 |
}
|
| 1137 |
]
|
|
|
|
| 1140 |
if 'current_likert_group' not in st.session_state:
|
| 1141 |
st.session_state.current_likert_group = 0
|
| 1142 |
st.session_state.answers = st.session_state.get('answers', {})
|
| 1143 |
+
|
| 1144 |
# دریافت گروه جاری
|
| 1145 |
current_group = question_groups[st.session_state.current_likert_group]
|
| 1146 |
+
|
| 1147 |
# نمایش عنوان گروه
|
| 1148 |
st.markdown(f"## {current_group['title']}")
|
| 1149 |
st.markdown("---")
|
| 1150 |
+
|
| 1151 |
+
# ثبت پاسخهای جاری
|
| 1152 |
+
all_answered = True
|
| 1153 |
for question in current_group['questions']:
|
| 1154 |
+
value = custom_likert_slider(
|
| 1155 |
+
question=question["question"],
|
| 1156 |
+
key=question["key"],
|
| 1157 |
+
points=question["scale"]
|
| 1158 |
)
|
| 1159 |
+
if value is None:
|
| 1160 |
+
all_answered = False
|
| 1161 |
+
else:
|
| 1162 |
+
st.session_state.answers[question["key"]] = value
|
| 1163 |
+
|
| 1164 |
+
# دکمه ادامه یا اتمام
|
| 1165 |
+
button_label = "ادامه به گروه بعدی" if st.session_state.current_likert_group < len(question_groups) - 1 else "اتمام پرسشنامه"
|
| 1166 |
|
| 1167 |
if st.button(button_label, disabled=not all_answered):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1168 |
if st.session_state.current_likert_group < len(question_groups) - 1:
|
| 1169 |
st.session_state.current_likert_group += 1
|
| 1170 |
st.rerun()
|
|
|
|
| 1173 |
st.rerun()
|
| 1174 |
|
| 1175 |
if not all_answered:
|
| 1176 |
+
st.warning("لطفاً به تمام سوالات این بخش پاسخ دهید.")
|
| 1177 |
+
|
| 1178 |
def explanation_questions():
|
| 1179 |
"""سوالات درباره توضیحات قیمت"""
|
| 1180 |
st.markdown("""
|
|
|
|
| 1375 |
|
| 1376 |
# ========== مدیریت وضعیت و صفحهبندی ==========
|
| 1377 |
def main():
|
|
|
|
|
|
|
| 1378 |
# تشخیص دستگاه
|
| 1379 |
user_agent = st.query_params.get("user_agent", [""])[0]
|
| 1380 |
st.session_state.is_desktop = "mobile" not in user_agent.lower()
|
| 1381 |
|
| 1382 |
|
|
|
|
| 1383 |
if st.session_state.is_desktop:
|
| 1384 |
# اطمینان از نمایش همان حالت موبایل برای همه دستگاهها
|
| 1385 |
st.session_state.is_desktop = False
|