|
|
import streamlit as st |
|
|
from supabase import create_client, Client |
|
|
import random |
|
|
import time |
|
|
import io |
|
|
import uuid |
|
|
from PIL import Image, ImageDraw, ImageFont |
|
|
import pandas as pd |
|
|
from datetime import datetime |
|
|
import os |
|
|
|
|
|
|
|
|
st.set_page_config( |
|
|
page_title="Number Assignment & Form System", |
|
|
layout="centered", |
|
|
initial_sidebar_state="expanded" |
|
|
) |
|
|
|
|
|
|
|
|
st.markdown(""" |
|
|
<style> |
|
|
.main-header {text-align: center; margin-bottom: 30px;} |
|
|
.number-display {font-size: 72px; text-align: center; margin: 30px 0;} |
|
|
.success-msg {background-color: #d4edda; color: #155724; padding: 10px; border-radius: 5px;} |
|
|
.error-msg {background-color: #f8d7da; color: #721c24; padding: 10px; border-radius: 5px;} |
|
|
.sub-header {font-size: 1.5em; color: #2196F3;} |
|
|
</style> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
|
|
|
def get_supabase_client() -> Client: |
|
|
"""Estabelece conexão com o Supabase usando variáveis de ambiente.""" |
|
|
supabase_url = os.getenv("SUPABASE_URL") |
|
|
supabase_key = os.getenv("SUPABASE_KEY") |
|
|
if not supabase_url or not supabase_key: |
|
|
st.error("Credenciais do Supabase não configuradas no ambiente.") |
|
|
return None |
|
|
try: |
|
|
client = create_client(supabase_url, supabase_key) |
|
|
client.table("_dummy").select("*").limit(1).execute() |
|
|
return client |
|
|
except Exception as e: |
|
|
st.error(f"Erro ao conectar ao Supabase: {str(e)}") |
|
|
return None |
|
|
|
|
|
def check_table_exists(supabase, table_name): |
|
|
"""Verifica se uma tabela específica existe no Supabase.""" |
|
|
try: |
|
|
supabase.table(table_name).select("*").limit(1).execute() |
|
|
return True |
|
|
except Exception: |
|
|
return False |
|
|
|
|
|
def create_meeting_table(supabase, table_name, meeting_name, max_number=999, selected_forms=None): |
|
|
"""Cria uma nova tabela para uma reunião no Supabase e registra metadados.""" |
|
|
try: |
|
|
response_metadata = supabase.table("meetings_metadata").insert({ |
|
|
"table_name": table_name, |
|
|
"meeting_name": meeting_name, |
|
|
"created_at": datetime.now().isoformat(), |
|
|
"max_number": max_number |
|
|
}).execute() |
|
|
meeting_id = response_metadata.data[0]["id"] |
|
|
|
|
|
create_table_query = f""" |
|
|
CREATE TABLE public.{table_name} ( |
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, |
|
|
number INTEGER NOT NULL, |
|
|
assigned BOOLEAN DEFAULT FALSE, |
|
|
assigned_at TIMESTAMPTZ, |
|
|
user_id TEXT |
|
|
); |
|
|
""" |
|
|
supabase.rpc("execute_sql", {"query": create_table_query}).execute() |
|
|
|
|
|
time.sleep(1) |
|
|
if not check_table_exists(supabase, table_name): |
|
|
raise Exception(f"Tabela {table_name} não foi criada com sucesso no Supabase.") |
|
|
|
|
|
batch_size = 100 |
|
|
for i in range(0, max_number, batch_size): |
|
|
end = min(i + batch_size, max_number) |
|
|
data = [{"number": j, "assigned": False, "assigned_at": None, "user_id": None} |
|
|
for j in range(i+1, end+1)] |
|
|
supabase.table(table_name).insert(data).execute() |
|
|
|
|
|
|
|
|
if selected_forms: |
|
|
for form_id in selected_forms: |
|
|
supabase.table("meeting_forms").insert({ |
|
|
"meeting_id": meeting_id, |
|
|
"form_id": form_id |
|
|
}).execute() |
|
|
|
|
|
return True |
|
|
except Exception as e: |
|
|
st.error(f"Erro ao criar tabela de reunião: {str(e)}") |
|
|
try: |
|
|
supabase.table("meetings_metadata").delete().eq("table_name", table_name).execute() |
|
|
supabase.rpc("execute_sql", {"query": f"DROP TABLE IF EXISTS public.{table_name}"}).execute() |
|
|
except Exception as rollback_e: |
|
|
st.error(f"Erro no rollback: {str(rollback_e)}") |
|
|
return False |
|
|
|
|
|
def get_available_meetings(supabase): |
|
|
"""Recupera a lista de reuniões disponíveis da tabela de metadados.""" |
|
|
try: |
|
|
response = supabase.table("meetings_metadata").select("*").execute() |
|
|
return response.data if response.data else [] |
|
|
except Exception as e: |
|
|
st.error(f"Erro ao recuperar reuniões: {str(e)}") |
|
|
return [] |
|
|
|
|
|
def get_available_forms(supabase): |
|
|
"""Recupera a lista de formulários disponíveis da tabela de metadados.""" |
|
|
try: |
|
|
response = supabase.table("forms_metadata").select("*").execute() |
|
|
return response.data if response.data else [] |
|
|
except Exception as e: |
|
|
st.error(f"Erro ao recuperar formulários: {str(e)}") |
|
|
return [] |
|
|
|
|
|
def get_forms_for_meeting(supabase, meeting_id): |
|
|
"""Recupera os formulários associados a uma reunião específica.""" |
|
|
try: |
|
|
response = supabase.table("meeting_forms").select("form_id").eq("meeting_id", meeting_id).execute() |
|
|
form_ids = [row["form_id"] for row in response.data] |
|
|
if form_ids: |
|
|
forms = supabase.table("forms_metadata").select("*").in_("id", form_ids).execute() |
|
|
return forms.data if forms.data else [] |
|
|
return [] |
|
|
except Exception as e: |
|
|
st.error(f"Erro ao recuperar formulários da reunião: {str(e)}") |
|
|
return [] |
|
|
|
|
|
def get_answered_forms(supabase, participant_id): |
|
|
"""Recupera os IDs dos formulários já respondidos por um participant_id.""" |
|
|
try: |
|
|
response = supabase.table("responses").select("form_id").eq("participant_id", participant_id).execute() |
|
|
return set(row["form_id"] for row in response.data) if response.data else set() |
|
|
except Exception as e: |
|
|
st.error(f"Erro ao verificar formulários respondidos: {str(e)}") |
|
|
return set() |
|
|
|
|
|
def generate_number_image(number): |
|
|
"""Gera uma imagem com o número atribuído.""" |
|
|
width, height = 600, 300 |
|
|
img = Image.new("RGB", (width, height), color=(255, 255, 255)) |
|
|
draw = ImageDraw.Draw(img) |
|
|
|
|
|
for y in range(height): |
|
|
r = int(220 - y/3) |
|
|
g = int(240 - y/3) |
|
|
b = 255 |
|
|
for x in range(width): |
|
|
draw.point((x, y), fill=(r, g, b)) |
|
|
|
|
|
try: |
|
|
font = ImageFont.truetype("Arial.ttf", 200) |
|
|
except IOError: |
|
|
font = ImageFont.load_default() |
|
|
|
|
|
number_text = str(number) |
|
|
bbox = draw.textbbox((0, 0), number_text, font=font) |
|
|
text_width = bbox[2] - bbox[0] |
|
|
text_height = bbox[3] - bbox[1] |
|
|
text_position = ((width - text_width) // 2, (height - text_height) // 2) |
|
|
draw.text(text_position, number_text, font=font, fill=(0, 0, 100)) |
|
|
|
|
|
img_buffer = io.BytesIO() |
|
|
img.save(img_buffer, format="PNG") |
|
|
img_buffer.seek(0) |
|
|
return img_buffer |
|
|
|
|
|
def generate_participant_link(table_name, user_id=None, mode="participant"): |
|
|
"""Gera um link para participantes acessarem a reunião ou formulário.""" |
|
|
base_url = "https://mynumber.streamlit.app" |
|
|
if user_id: |
|
|
return f"{base_url}/?table={table_name}&mode={mode}&user_id={user_id}" |
|
|
return f"{base_url}/?table={table_name}&mode={mode}" |
|
|
|
|
|
|
|
|
query_params = st.query_params |
|
|
mode = query_params.get("mode", "master") |
|
|
table_name_from_url = query_params.get("table", None) |
|
|
|
|
|
if "user_id" not in st.session_state: |
|
|
user_id_from_url = query_params.get("user_id", None) |
|
|
if user_id_from_url: |
|
|
st.session_state["user_id"] = user_id_from_url |
|
|
else: |
|
|
st.session_state["user_id"] = str(uuid.uuid4()) |
|
|
|
|
|
if mode == "participant" and table_name_from_url: |
|
|
|
|
|
st.markdown("<h1 class='main-header'>Obtenha Seu Número</h1>", unsafe_allow_html=True) |
|
|
supabase = get_supabase_client() |
|
|
if not supabase: |
|
|
st.stop() |
|
|
|
|
|
if not check_table_exists(supabase, table_name_from_url): |
|
|
st.error("Reunião não encontrada ou inválida.") |
|
|
st.stop() |
|
|
|
|
|
try: |
|
|
meeting_info = supabase.table("meetings_metadata").select("*").eq("table_name", table_name_from_url).execute() |
|
|
meeting_name = meeting_info.data[0]["meeting_name"] if meeting_info.data else "Reunião" |
|
|
meeting_id = meeting_info.data[0]["id"] |
|
|
st.subheader(f"Reunião: {meeting_name}") |
|
|
except Exception: |
|
|
st.subheader("Obtenha um número para a reunião") |
|
|
st.stop() |
|
|
|
|
|
user_id = st.session_state["user_id"] |
|
|
|
|
|
participant_link = generate_participant_link(table_name_from_url, user_id, mode="participant") |
|
|
st.markdown(f"**Seu Link Persistente para Reunião:** [{participant_link}]({participant_link})") |
|
|
st.write("Guarde este link para acessar sempre o mesmo número!") |
|
|
|
|
|
try: |
|
|
existing = supabase.table(table_name_from_url).select("number").eq("user_id", user_id).execute() |
|
|
if existing.data: |
|
|
st.session_state["assigned_number"] = existing.data[0]["number"] |
|
|
else: |
|
|
with st.spinner("Atribuindo um número..."): |
|
|
response = supabase.table(table_name_from_url).select("*").eq("assigned", False).execute() |
|
|
if response.data: |
|
|
available_numbers = [row["number"] for row in response.data] |
|
|
if available_numbers: |
|
|
assigned_number = random.choice(available_numbers) |
|
|
supabase.table(table_name_from_url).update({ |
|
|
"assigned": True, |
|
|
"assigned_at": datetime.now().isoformat(), |
|
|
"user_id": user_id |
|
|
}).eq("number", assigned_number).execute() |
|
|
st.session_state["assigned_number"] = assigned_number |
|
|
else: |
|
|
st.error("Todos os números foram atribuídos!") |
|
|
st.stop() |
|
|
else: |
|
|
st.error("Todos os números foram atribuídos!") |
|
|
st.stop() |
|
|
|
|
|
st.markdown(f""" |
|
|
<div class='success-msg'> |
|
|
<p>Seu número atribuído é:</p> |
|
|
<div class='number-display'>{st.session_state['assigned_number']}</div> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
st.subheader("Formulários Disponíveis para Você") |
|
|
forms = get_forms_for_meeting(supabase, meeting_id) |
|
|
participant_id = str(st.session_state["assigned_number"]) |
|
|
answered_forms = get_answered_forms(supabase, participant_id) |
|
|
if forms: |
|
|
for form in forms: |
|
|
form_id = form["id"] |
|
|
form_link = generate_participant_link(form["table_name"], user_id, mode="participant_form") |
|
|
status = "✅ Respondido" if form_id in answered_forms else "⏳ Pendente" |
|
|
st.markdown(f"- **{form['form_name']}** ({status}): [{form_link}]({form_link})") |
|
|
else: |
|
|
st.info("Nenhum formulário disponível para esta reunião.") |
|
|
|
|
|
except Exception as e: |
|
|
st.error(f"Erro ao atribuir número: {str(e)}") |
|
|
st.stop() |
|
|
|
|
|
if st.button("Salvar como Imagem"): |
|
|
with st.spinner("Gerando imagem..."): |
|
|
img_buffer = generate_number_image(st.session_state["assigned_number"]) |
|
|
st.image(img_buffer) |
|
|
st.download_button( |
|
|
"Baixar Imagem", |
|
|
img_buffer, |
|
|
file_name=f"meu_numero_{st.session_state['assigned_number']}.png", |
|
|
mime="image/png" |
|
|
) |
|
|
|
|
|
elif mode == "participant_form" and table_name_from_url: |
|
|
|
|
|
st.markdown("<h1 class='main-header'>Responder Formulário</h1>", unsafe_allow_html=True) |
|
|
supabase = get_supabase_client() |
|
|
if not supabase: |
|
|
st.stop() |
|
|
|
|
|
form_info = supabase.table("forms_metadata").select("*").eq("table_name", table_name_from_url).execute() |
|
|
if not form_info.data: |
|
|
st.error("Formulário não encontrado.") |
|
|
st.stop() |
|
|
|
|
|
form_id = form_info.data[0]['id'] |
|
|
st.subheader(f"Formulário: {form_info.data[0]['form_name']}") |
|
|
|
|
|
questions = supabase.table("questions").select("*").eq("form_id", form_id).execute() |
|
|
if not questions.data: |
|
|
st.error("Nenhuma pergunta encontrada para este formulário.") |
|
|
st.stop() |
|
|
|
|
|
user_id = st.session_state["user_id"] |
|
|
participant_id_default = "" |
|
|
meeting_table_name = "" |
|
|
for meeting in get_available_meetings(supabase): |
|
|
assigned = supabase.table(meeting["table_name"]).select("number").eq("user_id", user_id).execute() |
|
|
if assigned.data: |
|
|
participant_id_default = str(assigned.data[0]["number"]) |
|
|
meeting_table_name = meeting["table_name"] |
|
|
break |
|
|
|
|
|
if not participant_id_default: |
|
|
st.error("Você precisa ter um número atribuído para responder formulários.") |
|
|
st.stop() |
|
|
|
|
|
|
|
|
participant_id = participant_id_default |
|
|
answered_forms = get_answered_forms(supabase, participant_id) |
|
|
if form_id in answered_forms: |
|
|
st.warning("Você já respondeu este formulário. Cada participante só pode responder uma vez.") |
|
|
st.stop() |
|
|
|
|
|
with st.form("form_submission"): |
|
|
responses = {} |
|
|
for q in questions.data: |
|
|
st.write(f"{q['question_text']}") |
|
|
if q['question_type'] == 'text': |
|
|
responses[q['id']] = st.text_input("Sua resposta", key=f"resp_{q['id']}") |
|
|
elif q['question_type'] == 'multiple_choice': |
|
|
options = supabase.table("options").select("*").eq("question_id", q['id']).execute() |
|
|
option_texts = [opt['option_text'] for opt in options.data] |
|
|
option_ids = [opt['id'] for opt in options.data] |
|
|
selected_option = st.selectbox("Escolha uma opção", option_texts, key=f"resp_{q['id']}") |
|
|
responses[q['id']] = option_ids[option_texts.index(selected_option)] |
|
|
|
|
|
st.text_input("Seu Nome ou ID", value=participant_id, key="participant_id", disabled=True) |
|
|
if st.form_submit_button("Enviar"): |
|
|
if all(responses.values()): |
|
|
for q_id, answer in responses.items(): |
|
|
response_data = { |
|
|
"form_id": form_id, |
|
|
"participant_id": participant_id, |
|
|
"question_id": q_id, |
|
|
"answer": str(answer) |
|
|
} |
|
|
supabase.table("responses").insert(response_data).execute() |
|
|
st.success("Respostas enviadas com sucesso!") |
|
|
st.markdown(f"Voltando para sua página de participante em 3 segundos...") |
|
|
time.sleep(3) |
|
|
st.query_params.update({ |
|
|
"table": meeting_table_name, |
|
|
"mode": "participant", |
|
|
"user_id": user_id |
|
|
}) |
|
|
st.rerun() |
|
|
else: |
|
|
st.warning("Preencha todas as respostas.") |
|
|
|
|
|
else: |
|
|
|
|
|
valid_pages = ["Gerenciar Reuniões", "Compartilhar Link da Reunião", "Ver Estatísticas", "Gerenciar Formulários", "Compartilhar Link do Formulário"] |
|
|
if "page" not in st.session_state or st.session_state["page"] not in valid_pages: |
|
|
st.session_state["page"] = "Gerenciar Reuniões" |
|
|
|
|
|
st.sidebar.title("Menu (Master)") |
|
|
page = st.sidebar.radio("Escolha uma opção", valid_pages, index=valid_pages.index(st.session_state["page"])) |
|
|
|
|
|
|
|
|
if page == "Gerenciar Reuniões": |
|
|
st.session_state["page"] = "Gerenciar Reuniões" |
|
|
st.markdown("<h1 class='main-header'>Gerenciar Reuniões</h1>", unsafe_allow_html=True) |
|
|
supabase = get_supabase_client() |
|
|
if not supabase: |
|
|
st.stop() |
|
|
|
|
|
with st.form("create_meeting_form"): |
|
|
st.subheader("Criar Nova Reunião") |
|
|
meeting_name = st.text_input("Nome da Reunião") |
|
|
max_number = st.number_input("Número Máximo", min_value=10, max_value=10000, value=999) |
|
|
|
|
|
|
|
|
forms = get_available_forms(supabase) |
|
|
form_options = {f"{f['form_name']} ({f['table_name']})": f["id"] for f in forms} |
|
|
selected_forms = st.multiselect("Formulários Disponíveis nesta Reunião", list(form_options.keys())) |
|
|
selected_form_ids = [form_options[form] for form in selected_forms] if selected_forms else None |
|
|
|
|
|
submit_button = st.form_submit_button("Criar Reunião") |
|
|
|
|
|
if submit_button: |
|
|
if meeting_name: |
|
|
table_name = f"meeting_{int(time.time())}_{meeting_name.lower().replace(' ', '_')}" |
|
|
if check_table_exists(supabase, table_name): |
|
|
st.error("Uma reunião com esse nome já existe. Tente outro nome.") |
|
|
else: |
|
|
with st.spinner("Criando reunião..."): |
|
|
success = create_meeting_table(supabase, table_name, meeting_name, max_number, selected_form_ids) |
|
|
if success: |
|
|
participant_link = generate_participant_link(table_name, mode="participant") |
|
|
st.success(f"Reunião '{meeting_name}' criada com sucesso!") |
|
|
st.markdown(f"**Link para Participantes:** [{participant_link}]({participant_link})") |
|
|
st.session_state["selected_table"] = table_name |
|
|
st.session_state["page"] = "Compartilhar Link da Reunião" |
|
|
st.rerun() |
|
|
else: |
|
|
st.error("Falha ao criar a reunião.") |
|
|
else: |
|
|
st.warning("Por favor, insira um nome para a reunião.") |
|
|
|
|
|
st.subheader("Reuniões Existentes") |
|
|
meetings = get_available_meetings(supabase) |
|
|
if meetings: |
|
|
meeting_data = [] |
|
|
for meeting in meetings: |
|
|
if "table_name" in meeting and "meeting_name" in meeting: |
|
|
table_name = meeting["table_name"] |
|
|
if check_table_exists(supabase, table_name): |
|
|
try: |
|
|
count_response = supabase.table(table_name).select("*", count="exact").eq("assigned", True).execute() |
|
|
assigned_count = count_response.count if hasattr(count_response, 'count') else 0 |
|
|
participant_link = generate_participant_link(table_name, mode="participant") |
|
|
meeting_data.append({ |
|
|
"Nome": meeting.get("meeting_name", "Sem nome"), |
|
|
"Tabela": table_name, |
|
|
"Link": participant_link, |
|
|
"Criada em": meeting.get("created_at", "")[:16].replace("T", " "), |
|
|
"Números Atribuídos": assigned_count, |
|
|
"Total de Números": meeting.get("max_number", 0) |
|
|
}) |
|
|
except Exception as e: |
|
|
st.warning(f"Erro ao processar reunião {table_name}: {str(e)}") |
|
|
if meeting_data: |
|
|
df = pd.DataFrame(meeting_data) |
|
|
st.dataframe(df) |
|
|
else: |
|
|
st.info("Nenhuma reunião válida encontrada.") |
|
|
else: |
|
|
st.info("Nenhuma reunião disponível ou erro ao acessar o Supabase.") |
|
|
|
|
|
|
|
|
elif page == "Compartilhar Link da Reunião": |
|
|
st.session_state["page"] = "Compartilhar Link da Reunião" |
|
|
st.markdown("<h1 class='main-header'>Compartilhar Link da Reunião</h1>", unsafe_allow_html=True) |
|
|
|
|
|
supabase = get_supabase_client() |
|
|
if not supabase: |
|
|
st.stop() |
|
|
|
|
|
meetings = get_available_meetings(supabase) |
|
|
if not meetings: |
|
|
st.info("Nenhuma reunião disponível. Crie uma reunião primeiro.") |
|
|
st.stop() |
|
|
|
|
|
options = {f"{m['meeting_name']} ({m['table_name']})": m["table_name"] |
|
|
for m in meetings if "table_name" in m and "meeting_name" in m} |
|
|
selected = st.selectbox("Selecione uma reunião para compartilhar:", list(options.keys())) |
|
|
|
|
|
if selected: |
|
|
selected_table = options[selected] |
|
|
participant_link = generate_participant_link(selected_table, mode="participant") |
|
|
st.markdown(f"**Link para Participantes:** [{participant_link}]({participant_link})") |
|
|
if st.button("Copiar Link"): |
|
|
st.write("Link copiado para a área de transferência!") |
|
|
st.code(participant_link) |
|
|
|
|
|
|
|
|
elif page == "Ver Estatísticas": |
|
|
st.session_state["page"] = "Ver Estatísticas" |
|
|
st.markdown("<h1 class='main-header'>Estatísticas da Reunião</h1>", unsafe_allow_html=True) |
|
|
supabase = get_supabase_client() |
|
|
if not supabase: |
|
|
st.stop() |
|
|
|
|
|
meetings = get_available_meetings(supabase) |
|
|
if not meetings: |
|
|
st.info("Nenhuma reunião disponível para análise.") |
|
|
st.stop() |
|
|
|
|
|
options = {f"{m['meeting_name']} ({m['table_name']})": m["table_name"] |
|
|
for m in meetings if "table_name" in m and "meeting_name" in m} |
|
|
selected = st.selectbox("Selecione uma reunião:", list(options.keys())) |
|
|
|
|
|
if selected: |
|
|
selected_table = options[selected] |
|
|
meeting_info = supabase.table("meetings_metadata").select("id").eq("table_name", selected_table).execute() |
|
|
meeting_id = meeting_info.data[0]["id"] |
|
|
|
|
|
|
|
|
try: |
|
|
total_response = supabase.table(selected_table).select("*", count="exact").execute() |
|
|
total_numbers = total_response.count if hasattr(total_response, 'count') else 0 |
|
|
assigned_response = supabase.table(selected_table).select("*", count="exact").eq("assigned", True).execute() |
|
|
assigned_numbers = assigned_response.count if hasattr(assigned_response, 'count') else 0 |
|
|
percentage = (assigned_numbers / total_numbers) * 100 if total_numbers > 0 else 0 |
|
|
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
with col1: |
|
|
st.metric("Total de Números", total_numbers) |
|
|
with col2: |
|
|
st.metric("Números Atribuídos", assigned_numbers) |
|
|
with col3: |
|
|
st.metric("Porcentagem Atribuída", f"{percentage:.1f}%") |
|
|
|
|
|
try: |
|
|
time_data_response = supabase.table(selected_table).select("*").eq("assigned", True).order("assigned_at").execute() |
|
|
if time_data_response.data: |
|
|
time_data = [] |
|
|
for item in time_data_response.data: |
|
|
if item.get("assigned_at"): |
|
|
time_data.append({ |
|
|
"time": item.get("assigned_at")[:16].replace("T", " "), |
|
|
"count": 1 |
|
|
}) |
|
|
if time_data: |
|
|
df = pd.DataFrame(time_data) |
|
|
df["time"] = pd.to_datetime(df["time"]) |
|
|
df["hour"] = df["time"].dt.floor("H") |
|
|
hourly_counts = df.groupby("hour").count().reset_index() |
|
|
hourly_counts["hour_str"] = hourly_counts["hour"].dt.strftime("%m/%d %H:00") |
|
|
st.subheader("Atribuições de Números por Hora") |
|
|
st.bar_chart(data=hourly_counts, x="hour_str", y="count") |
|
|
except Exception: |
|
|
st.info("Dados temporais não disponíveis para esta reunião.") |
|
|
|
|
|
if st.button("Exportar Dados de Números"): |
|
|
try: |
|
|
all_data_response = supabase.table(selected_table).select("*").execute() |
|
|
if all_data_response.data: |
|
|
df = pd.DataFrame(all_data_response.data) |
|
|
csv = df.to_csv(index=False) |
|
|
st.download_button( |
|
|
"Baixar CSV", |
|
|
csv, |
|
|
file_name=f"{selected_table}_numeros_export.csv", |
|
|
mime="text/csv" |
|
|
) |
|
|
except Exception as e: |
|
|
st.error(f"Erro ao exportar dados: {str(e)}") |
|
|
except Exception as e: |
|
|
st.error(f"Erro ao recuperar estatísticas de números: {str(e)}") |
|
|
|
|
|
|
|
|
st.subheader("Respostas dos Formulários") |
|
|
forms = get_forms_for_meeting(supabase, meeting_id) |
|
|
if forms: |
|
|
form_ids = [f["id"] for f in forms] |
|
|
responses = supabase.table("responses").select("participant_id, form_id, question_id, answer").in_("form_id", form_ids).execute() |
|
|
if responses.data: |
|
|
response_data = [] |
|
|
for resp in responses.data: |
|
|
form = next((f for f in forms if f["id"] == resp["form_id"]), None) |
|
|
question = supabase.table("questions").select("question_text").eq("id", resp["question_id"]).execute().data[0] |
|
|
response_data.append({ |
|
|
"Participante": resp["participant_id"], |
|
|
"Formulário": form["form_name"] if form else "Desconhecido", |
|
|
"Pergunta": question["question_text"], |
|
|
"Resposta": resp["answer"] |
|
|
}) |
|
|
df = pd.DataFrame(response_data) |
|
|
st.dataframe(df) |
|
|
|
|
|
if st.button("Exportar Respostas dos Formulários"): |
|
|
csv = df.to_csv(index=False) |
|
|
st.download_button( |
|
|
"Baixar CSV", |
|
|
csv, |
|
|
file_name=f"{selected_table}_respostas_export.csv", |
|
|
mime="text/csv" |
|
|
) |
|
|
else: |
|
|
st.info("Nenhuma resposta registrada para os formulários desta reunião.") |
|
|
else: |
|
|
st.info("Nenhum formulário associado a esta reunião.") |
|
|
|
|
|
|
|
|
elif page == "Gerenciar Formulários": |
|
|
st.session_state["page"] = "Gerenciar Formulários" |
|
|
st.markdown("<h1 class='main-header'>Gerenciar Formulários</h1>", unsafe_allow_html=True) |
|
|
supabase = get_supabase_client() |
|
|
if not supabase: |
|
|
st.stop() |
|
|
|
|
|
with st.form("create_form_form"): |
|
|
st.subheader("Criar Novo Formulário") |
|
|
form_name = st.text_input("Nome do Formulário", key="form_name") |
|
|
|
|
|
if 'questions' not in st.session_state: |
|
|
st.session_state['questions'] = [] |
|
|
|
|
|
st.markdown("<h3 class='sub-header'>Adicionar Pergunta</h3>", unsafe_allow_html=True) |
|
|
question_type = st.selectbox("Tipo da Pergunta", ["Texto", "Múltipla Escolha"], key="q_type") |
|
|
question_text = st.text_input("Texto da Pergunta", key="q_text") |
|
|
|
|
|
correct_answer = None |
|
|
options = [] |
|
|
if question_type == "Múltipla Escolha": |
|
|
num_options = st.number_input("Número de Opções", min_value=2, max_value=10, value=2, key="num_opts") |
|
|
for i in range(num_options): |
|
|
option_text = st.text_input(f"Opção {i+1}", key=f"opt_{i}") |
|
|
if option_text: |
|
|
options.append(option_text) |
|
|
correct_option = st.selectbox("Opção Correta (opcional)", ["Nenhuma"] + options, key="correct_opt") |
|
|
if correct_option != "Nenhuma": |
|
|
correct_answer = correct_option |
|
|
else: |
|
|
correct_answer = st.text_input("Resposta Correta (opcional)", key="correct_text") |
|
|
|
|
|
if st.form_submit_button("Adicionar Pergunta"): |
|
|
if question_text: |
|
|
if question_type == "Múltipla Escolha" and len(options) >= 2: |
|
|
st.session_state['questions'].append({ |
|
|
'type': 'multiple_choice', |
|
|
'text': question_text, |
|
|
'options': options, |
|
|
'correct': correct_answer |
|
|
}) |
|
|
st.success("Pergunta de múltipla escolha adicionada!") |
|
|
elif question_type == "Texto": |
|
|
st.session_state['questions'].append({ |
|
|
'type': 'text', |
|
|
'text': question_text, |
|
|
'correct': correct_answer if correct_answer else None |
|
|
}) |
|
|
st.success("Pergunta de texto adicionada!") |
|
|
else: |
|
|
st.warning("Adicione pelo menos 2 opções para perguntas de múltipla escolha.") |
|
|
else: |
|
|
st.warning("O texto da pergunta é obrigatório.") |
|
|
|
|
|
if st.session_state['questions']: |
|
|
st.markdown("<h3 class='sub-header'>Perguntas Adicionadas</h3>", unsafe_allow_html=True) |
|
|
for i, q in enumerate(st.session_state['questions']): |
|
|
st.write(f"{i+1}. {q['text']} ({q['type']})") |
|
|
if q['type'] == 'multiple_choice': |
|
|
st.write("Opções:", ", ".join(q['options'])) |
|
|
st.write(f"Correta: {q['correct'] if q['correct'] else 'Nenhuma'}") |
|
|
else: |
|
|
st.write(f"Correta: {q['correct'] if q['correct'] else 'Nenhuma'}") |
|
|
|
|
|
if st.form_submit_button("Criar Formulário"): |
|
|
if form_name and st.session_state['questions']: |
|
|
table_name = f"form_{int(time.time())}_{form_name.lower().replace(' ', '_')}" |
|
|
form_data = {"form_name": form_name, "table_name": table_name, "created_at": datetime.now().isoformat()} |
|
|
form_response = supabase.table("forms_metadata").insert(form_data).execute() |
|
|
form_id = form_response.data[0]['id'] |
|
|
|
|
|
for q in st.session_state['questions']: |
|
|
question_data = { |
|
|
"form_id": form_id, |
|
|
"question_text": q['text'], |
|
|
"question_type": q['type'], |
|
|
"correct_answer": q['correct'] |
|
|
} |
|
|
q_response = supabase.table("questions").insert(question_data).execute() |
|
|
question_id = q_response.data[0]['id'] |
|
|
|
|
|
if q['type'] == 'multiple_choice': |
|
|
for opt in q['options']: |
|
|
opt_data = {"question_id": question_id, "option_text": opt} |
|
|
opt_response = supabase.table("options").insert(opt_data).execute() |
|
|
if opt == q['correct']: |
|
|
supabase.table("questions").update({"correct_answer": str(opt_response.data[0]['id'])}).eq("id", question_id).execute() |
|
|
|
|
|
participant_link = generate_participant_link(table_name, mode="participant_form") |
|
|
st.success(f"Formulário '{form_name}' criado com sucesso!") |
|
|
st.markdown(f"**Link Geral para Participantes:** [{participant_link}]({participant_link})") |
|
|
st.session_state['questions'] = [] |
|
|
st.session_state["selected_form_table"] = table_name |
|
|
st.session_state["page"] = "Compartilhar Link do Formulário" |
|
|
st.rerun() |
|
|
else: |
|
|
st.warning("Insira um nome para o formulário e pelo menos uma pergunta.") |
|
|
|
|
|
st.subheader("Formulários Disponíveis") |
|
|
forms = get_available_forms(supabase) |
|
|
if forms: |
|
|
form_data = [] |
|
|
for form in forms: |
|
|
participant_link = generate_participant_link(form["table_name"], mode="participant_form") |
|
|
form_data.append({ |
|
|
"Nome": form["form_name"], |
|
|
"Link Geral": participant_link, |
|
|
"Criado em": form["created_at"][:16].replace("T", " ") |
|
|
}) |
|
|
df = pd.DataFrame(form_data) |
|
|
st.dataframe(df, column_config={"Link Geral": st.column_config.LinkColumn("Link Geral")}) |
|
|
else: |
|
|
st.info("Nenhum formulário disponível.") |
|
|
|
|
|
|
|
|
elif page == "Compartilhar Link do Formulário": |
|
|
st.session_state["page"] = "Compartilhar Link do Formulário" |
|
|
st.markdown("<h1 class='main-header'>Compartilhar Link do Formulário</h1>", unsafe_allow_html=True) |
|
|
|
|
|
supabase = get_supabase_client() |
|
|
if not supabase: |
|
|
st.stop() |
|
|
|
|
|
forms = get_available_forms(supabase) |
|
|
if not forms: |
|
|
st.info("Nenhum formulário disponível. Crie um formulário primeiro.") |
|
|
st.stop() |
|
|
|
|
|
options = {f"{f['form_name']} ({f['table_name']})": f["table_name"] |
|
|
for f in forms if "table_name" in f and "form_name" in f} |
|
|
selected = st.selectbox("Selecione um formulário para compartilhar:", list(options.keys())) |
|
|
|
|
|
if selected: |
|
|
selected_table = options[selected] |
|
|
participant_link = generate_participant_link(selected_table, mode="participant_form") |
|
|
st.markdown(f"**Link Geral para Participantes:** [{participant_link}]({participant_link})") |
|
|
if st.button("Copiar Link Geral"): |
|
|
st.write("Link copiado para a área de transferência!") |
|
|
st.code(participant_link) |
|
|
|
|
|
st.subheader("Links Únicos por Usuário") |
|
|
meetings = get_available_meetings(supabase) |
|
|
user_links = [] |
|
|
for meeting in meetings: |
|
|
assigned_users = supabase.table(meeting["table_name"]).select("user_id, number").eq("assigned", True).execute() |
|
|
for user in assigned_users.data: |
|
|
user_link = generate_participant_link(selected_table, user["user_id"], mode="participant_form") |
|
|
user_links.append({"Número": user["number"], "Link": user_link}) |
|
|
if user_links: |
|
|
df = pd.DataFrame(user_links) |
|
|
st.dataframe(df, column_config={"Link": st.column_config.LinkColumn("Link")}) |
|
|
else: |
|
|
st.info("Nenhum usuário com número atribuído encontrado.") |
|
|
|
|
|
if __name__ == "__main__": |
|
|
pass |
|
|
|