keyinnovations's picture
Upload 2 files
d2db761 verified
import streamlit as st
import json
import requests
import streamlit.components.v1 as components
import datetime
st.set_page_config(
page_title="Key Innovations Inc.",
page_icon="https://tscstatic.keyinnovations.ca/logo/logo_1T895ONEIG.png",
layout="wide"
)
st.image("https://tscstatic.keyinnovations.ca/logo/logo_1T895ONEIG.png", width=150)
#st.title('Syncore Calendar')
st.subheader('Syncore CalendarπŸ“…')
# API Endpoint
ALL_JOBS_URL = "https://api.syncore.app/v2/orders/jobs?date_from={}&date_to={}&limit=1000"
SALES_ORDERS_URL = "https://api.syncore.app/v2/orders/jobs/{}/salesorders"
# API Headers
HEADERS = {
"x-api-key": "69c23811-1073-4c1c-9902-8d552703c460",
"Accept": "application/json"
}
# Function to fetch jobs for the selected month with pagination
def fetch_jobs(selected_year, selected_month):
first_day = datetime.date(selected_year, selected_month, 1)
last_day = (first_day + datetime.timedelta(days=32)).replace(day=1) - datetime.timedelta(days=1)
date_from, date_to = first_day.strftime("%Y-%m-%d"), last_day.strftime("%Y-%m-%d")
all_jobs = []
page = 1
while True:
url = f"https://api.syncore.app/v2/orders/jobs?date_from={date_from}&date_to={date_to}&limit=100&page={page}"
response = requests.get(url, headers=HEADERS)
if response.status_code == 200:
data = response.json()
new_jobs = data.get("jobs", [])
if not new_jobs:
break
all_jobs.extend(new_jobs)
page += 1
else:
st.error(f"API Error (Fetching Jobs): {response.status_code} - {response.text}")
break
return [job["id"] for job in all_jobs]
# Function to fetch sales orders for jobs
def fetch_sales_orders_for_jobs(job_ids):
orders = []
priority_colors = {"High": "#dc2626", "Medium": "#2563eb", "Low": "#22c55e"}
today = datetime.date.today()
processed_alerts = set()
for job_id in job_ids:
url = SALES_ORDERS_URL.format(job_id)
response = requests.get(url, headers=HEADERS)
if response.status_code == 200:
data = response.json()
if "salesorders" in data:
for order in data["salesorders"]:
job_created = order.get("date", "Not Available")
estimated_delivery_date = order.get("estimated_delivery_date", "Not Available")
job_number = order.get("job_number", "NA")
# Extract supplier names
supplier_names = list({
item["supplier"]["name"]
for item in order.get("line_items", []) if item.get("supplier")
})
if len(supplier_names) < 2:
#print(len(supplier_names))
continue
# Assign priority based on estimated delivery date
priority = "Low"
if estimated_delivery_date != "Not Available":
estimated_date = datetime.datetime.strptime(estimated_delivery_date, "%Y-%m-%d").date()
days_until_delivery = (estimated_date - today).days
if days_until_delivery <= 15:
priority = "High"
elif 16 <= days_until_delivery <= 30:
priority = "Medium"
# Add job event
if job_created != "Not Available":
job_event = {
"id": f"{job_number}-job",
"title": f"{job_number}<br><strong>πŸ›  Suppliers:</strong> {', '.join(supplier_names)}<br>πŸ“… {job_created} β†’ 🚚 {estimated_delivery_date}",
"start": job_created,
"color": priority_colors.get(priority, "#2563eb")
}
orders.append(job_event)
# Add delivery alert
if estimated_delivery_date != "Not Available" and job_id not in processed_alerts:
delivery_alert_event = {
"id": f"{job_number}-alert",
"title": f"{job_number}<br>🚨 Delivery Day",
"start": estimated_delivery_date,
"color": "#ff9800"
}
orders.append(delivery_alert_event)
processed_alerts.add(job_id)
else:
st.error(f"API Fetch Error (Sales Orders for Job {job_id}): {response.status_code}")
return orders
# Handle Calendar Month Switching
if "selected_month" not in st.session_state:
today = datetime.date.today()
st.session_state.selected_year = today.year
st.session_state.selected_month = today.month
params = st.query_params
if "year" in params and "month" in params:
st.session_state.selected_year = int(params["year"][0])
st.session_state.selected_month = int(params["month"][0])
# Show loading animation
with st.spinner("Fetching jobs... Please wait."):
job_ids = fetch_jobs(st.session_state.selected_year, st.session_state.selected_month)
with st.spinner("Processing sales orders... This may take up to 60 seconds"):
orders = fetch_sales_orders_for_jobs(job_ids)
st.balloons()
events_json = json.dumps(orders)
# Calendar with Priority Legend & Delivery Alerts
calendar_html = f"""
<!DOCTYPE html>
<html>
<head>
<link href="https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/main.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/main.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/locales-all.min.js"></script>
<style>
body {{
font-family: 'Poppins', sans-serif;
background-color: #f8fafc;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
height: 100vh;
padding: 20px;
}}
/* πŸ“Œ Priority Legend Styling */
.priority-legend {{
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
padding: 10px;
background: white;
border-radius: 8px;
margin-bottom: 15px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
width: 85vw;
max-width: 1200px;
}}
.priority-box {{
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: bold;
}}
.priority-box span {{
width: 16px;
height: 16px;
display: inline-block;
border-radius: 4px;
}}
#calendar-container {{
width: 100vw;
height: 100vh;
background: white;
box-shadow: none;
border-radius: 0;
padding: 0;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}}
#calendar {{
width: 95%;
height: 95%;
}}
.fc-event {{
border-radius: 6px;
padding: 6px;
font-size: 12px;
font-weight: bold;
white-space: normal;
text-align: left;
line-height: 1.5;
display: flex;
flex-direction: column;
justify-content: center;
background: rgba(0, 0, 0, 0.1);
border-left: 5px solid #2563eb;
color: black;
transition: all 0.2s ease-in-out;
box-shadow: 0px 3px 7px rgba(0, 0, 0, 0.12);
}}
.fc-event-title {{
padding: 6px;
background: white;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
white-space: normal;
line-height: 1.4;
text-align: left;
}}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {{
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {{
initialView: 'dayGridMonth',
editable: true,
selectable: true,
height: '100%',
themeSystem: 'standard',
headerToolbar: {{
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay'
}},
eventContent: function(arg) {{
return {{ html: arg.event.title }};
}},
events: {events_json},
buttonIcons: true,
}});
calendar.render();
}});
</script>
</head>
<body>
<!-- πŸ“Œ Priority Legend Section (Now Outside Calendar) -->
<div class="priority-legend">
<div class="priority-box">
<span style="background-color: #dc2626;"></span> High Priority
</div>
<div class="priority-box">
<span style="background-color: #2563eb;"></span> Medium Priority
</div>
<div class="priority-box">
<span style="background-color: #22c55e;"></span> Low Priority
</div>
<div class="priority-box">
<span style="background-color: #ff9800;"></span> Delivery Alert
</div>
</div>
<!-- πŸ“Œ Calendar Container -->
<div id='calendar-container'>
<div id='calendar'></div>
</div>
</body>
</html>
"""
# Display the FullCalendar.js
components.html(calendar_html, height=900, scrolling=False)