|
|
import gradio as gr |
|
|
import pandas as pd |
|
|
from datetime import datetime, timedelta |
|
|
from reportlab.lib.pagesizes import letter, A4 |
|
|
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image |
|
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle |
|
|
from reportlab.lib.units import inch |
|
|
from reportlab.lib import colors |
|
|
from reportlab.lib.enums import TA_CENTER, TA_LEFT |
|
|
import tempfile |
|
|
import os |
|
|
from PIL import Image as PILImage |
|
|
|
|
|
def generate_schedule(start_date, end_date, off_days, num_lessons, logo_file=None): |
|
|
""" |
|
|
Generate study schedule based on user inputs |
|
|
""" |
|
|
try: |
|
|
|
|
|
start = datetime.strptime(start_date, "%Y-%m-%d") |
|
|
end = datetime.strptime(end_date, "%Y-%m-%d") |
|
|
|
|
|
|
|
|
if start >= end: |
|
|
return "β Error: Start date must be before end date!", None, None |
|
|
|
|
|
|
|
|
day_mapping = { |
|
|
"Monday": 0, "Tuesday": 1, "Wednesday": 2, "Thursday": 3, |
|
|
"Friday": 4, "Saturday": 5, "Sunday": 6 |
|
|
} |
|
|
off_weekdays = [day_mapping[day] for day in off_days] |
|
|
|
|
|
|
|
|
valid_dates = [] |
|
|
current_date = start |
|
|
|
|
|
while current_date <= end: |
|
|
if current_date.weekday() not in off_weekdays: |
|
|
valid_dates.append(current_date) |
|
|
current_date += timedelta(days=1) |
|
|
|
|
|
if not valid_dates: |
|
|
return "β Error: No valid study days found in the selected range!", None, None |
|
|
|
|
|
if len(valid_dates) < num_lessons: |
|
|
return f"β Error: Not enough study days ({len(valid_dates)}) for {num_lessons} lessons. Please extend your date range or reduce off-days.", None, None |
|
|
|
|
|
|
|
|
schedule_data = [] |
|
|
lessons_per_day = [1] * len(valid_dates) |
|
|
|
|
|
|
|
|
if len(valid_dates) < num_lessons: |
|
|
extra_lessons = num_lessons - len(valid_dates) |
|
|
for i in range(extra_lessons): |
|
|
lessons_per_day[i % len(valid_dates)] += 1 |
|
|
|
|
|
|
|
|
lesson_counter = 1 |
|
|
for i, date in enumerate(valid_dates): |
|
|
if lesson_counter > num_lessons: |
|
|
break |
|
|
|
|
|
for _ in range(lessons_per_day[i]): |
|
|
if lesson_counter <= num_lessons: |
|
|
schedule_data.append({ |
|
|
"Date": date.strftime("%Y-%m-%d"), |
|
|
"Day": date.strftime("%A"), |
|
|
"Session": f"Session {lesson_counter}" |
|
|
}) |
|
|
lesson_counter += 1 |
|
|
|
|
|
|
|
|
df = pd.DataFrame(schedule_data) |
|
|
|
|
|
|
|
|
total_days = len(valid_dates) |
|
|
total_weeks = (end - start).days // 7 + 1 |
|
|
summary = f""" |
|
|
π **Schedule Summary:** |
|
|
β’ Total Lessons: {num_lessons} |
|
|
β’ Study Period: {start_date} to {end_date} |
|
|
β’ Available Study Days: {total_days} |
|
|
β’ Estimated Duration: ~{total_weeks} weeks |
|
|
β’ Off Days: {', '.join(off_days) if off_days else 'None'} |
|
|
""" |
|
|
|
|
|
return summary, df, schedule_data |
|
|
|
|
|
except ValueError as e: |
|
|
return f"β Error: Invalid date format. Please use YYYY-MM-DD format.", None, None |
|
|
except Exception as e: |
|
|
return f"β Error: {str(e)}", None, None |
|
|
|
|
|
def create_pdf(schedule_data, start_date, end_date, off_days, num_lessons, logo_file=None): |
|
|
""" |
|
|
Create PDF from schedule data and return file path |
|
|
""" |
|
|
try: |
|
|
|
|
|
pdf_file = tempfile.NamedTemporaryFile(delete=False, suffix='.pdf', prefix='study_schedule_') |
|
|
pdf_path = pdf_file.name |
|
|
pdf_file.close() |
|
|
|
|
|
|
|
|
doc = SimpleDocTemplate(pdf_path, pagesize=A4, rightMargin=72, leftMargin=72, |
|
|
topMargin=72, bottomMargin=72) |
|
|
|
|
|
|
|
|
elements = [] |
|
|
|
|
|
|
|
|
styles = getSampleStyleSheet() |
|
|
title_style = ParagraphStyle( |
|
|
'CustomTitle', |
|
|
parent=styles['Heading1'], |
|
|
fontSize=24, |
|
|
spaceAfter=30, |
|
|
alignment=TA_CENTER, |
|
|
textColor=colors.HexColor('#2E86AB') |
|
|
) |
|
|
|
|
|
subtitle_style = ParagraphStyle( |
|
|
'CustomSubtitle', |
|
|
parent=styles['Normal'], |
|
|
fontSize=12, |
|
|
spaceAfter=20, |
|
|
alignment=TA_CENTER, |
|
|
textColor=colors.HexColor('#666666') |
|
|
) |
|
|
|
|
|
|
|
|
if logo_file is not None: |
|
|
try: |
|
|
|
|
|
logo_temp = tempfile.NamedTemporaryFile(delete=False, suffix='.png') |
|
|
logo_temp.write(logo_file) |
|
|
logo_temp.close() |
|
|
|
|
|
|
|
|
logo = Image(logo_temp.name, width=2*inch, height=1*inch) |
|
|
logo.hAlign = 'CENTER' |
|
|
elements.append(logo) |
|
|
elements.append(Spacer(1, 20)) |
|
|
|
|
|
|
|
|
os.unlink(logo_temp.name) |
|
|
except Exception as e: |
|
|
print(f"Logo processing error: {e}") |
|
|
pass |
|
|
|
|
|
|
|
|
title = Paragraph("π Study Schedule", title_style) |
|
|
elements.append(title) |
|
|
|
|
|
|
|
|
subtitle_text = f""" |
|
|
Study Period: {start_date} to {end_date}<br/> |
|
|
Total Lessons: {num_lessons}<br/> |
|
|
Off Days: {', '.join(off_days) if off_days else 'None'} |
|
|
""" |
|
|
subtitle = Paragraph(subtitle_text, subtitle_style) |
|
|
elements.append(subtitle) |
|
|
elements.append(Spacer(1, 30)) |
|
|
|
|
|
|
|
|
table_data = [['Date', 'Day', 'Session']] |
|
|
for item in schedule_data: |
|
|
table_data.append([item['Date'], item['Day'], item['Session']]) |
|
|
|
|
|
|
|
|
table = Table(table_data, colWidths=[2*inch, 1.5*inch, 2*inch]) |
|
|
|
|
|
|
|
|
table.setStyle(TableStyle([ |
|
|
|
|
|
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#2E86AB')), |
|
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke), |
|
|
('ALIGN', (0, 0), (-1, -1), 'CENTER'), |
|
|
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), |
|
|
('FONTSIZE', (0, 0), (-1, 0), 12), |
|
|
|
|
|
|
|
|
('BACKGROUND', (0, 1), (-1, -1), colors.beige), |
|
|
('TEXTCOLOR', (0, 1), (-1, -1), colors.black), |
|
|
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'), |
|
|
('FONTSIZE', (0, 1), (-1, -1), 10), |
|
|
|
|
|
|
|
|
('GRID', (0, 0), (-1, -1), 1, colors.black), |
|
|
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), |
|
|
|
|
|
|
|
|
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#F5F5F5')]) |
|
|
])) |
|
|
|
|
|
elements.append(table) |
|
|
|
|
|
|
|
|
elements.append(Spacer(1, 30)) |
|
|
footer = Paragraph(f"Generated on {datetime.now().strftime('%Y-%m-%d %H:%M')}", |
|
|
styles['Normal']) |
|
|
elements.append(footer) |
|
|
|
|
|
|
|
|
doc.build(elements) |
|
|
|
|
|
return pdf_path |
|
|
|
|
|
except Exception as e: |
|
|
print(f"PDF creation error: {str(e)}") |
|
|
return None |
|
|
|
|
|
def process_schedule(start_date, end_date, off_days, num_lessons, logo_file): |
|
|
""" |
|
|
Main processing function for Gradio interface |
|
|
""" |
|
|
try: |
|
|
|
|
|
summary, df, schedule_data = generate_schedule(start_date, end_date, off_days, num_lessons, logo_file) |
|
|
|
|
|
if schedule_data is None: |
|
|
return summary, None, None, gr.update(visible=False) |
|
|
|
|
|
|
|
|
pdf_path = create_pdf(schedule_data, start_date, end_date, off_days, num_lessons, logo_file) |
|
|
|
|
|
if pdf_path is None: |
|
|
return summary, df, None, gr.update(visible=False) |
|
|
|
|
|
return summary, df, pdf_path, gr.update(visible=True) |
|
|
|
|
|
except Exception as e: |
|
|
error_msg = f"β Error processing schedule: {str(e)}" |
|
|
return error_msg, None, None, gr.update(visible=False) |
|
|
|
|
|
|
|
|
def create_interface(): |
|
|
with gr.Blocks( |
|
|
title="π Study Schedule Generator", |
|
|
theme=gr.themes.Soft(), |
|
|
css=""" |
|
|
.main-header { |
|
|
text-align: center; |
|
|
color: #2E86AB; |
|
|
margin-bottom: 2rem; |
|
|
} |
|
|
.info-box { |
|
|
background: #f0f9ff; |
|
|
padding: 1rem; |
|
|
border-radius: 8px; |
|
|
border-left: 4px solid #2E86AB; |
|
|
margin: 1rem 0; |
|
|
} |
|
|
.generate-btn { |
|
|
background: linear-gradient(45deg, #2E86AB, #A23B72) !important; |
|
|
border: none !important; |
|
|
color: white !important; |
|
|
font-weight: bold !important; |
|
|
font-size: 16px !important; |
|
|
padding: 12px 24px !important; |
|
|
border-radius: 8px !important; |
|
|
} |
|
|
""" |
|
|
) as demo: |
|
|
|
|
|
gr.Markdown( |
|
|
""" |
|
|
# π Study Schedule Generator |
|
|
|
|
|
<div class="info-box"> |
|
|
<strong>How it works:</strong><br> |
|
|
1. Set your study period (start and end dates)<br> |
|
|
2. Choose your off-days (days you don't want to study)<br> |
|
|
3. Set the number of lessons to schedule<br> |
|
|
4. Optionally upload a logo for your PDF<br> |
|
|
5. Generate and download your personalized study schedule as PDF! |
|
|
</div> |
|
|
""", |
|
|
elem_classes=["main-header"] |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1): |
|
|
gr.Markdown("### π
Schedule Settings") |
|
|
|
|
|
start_date = gr.Textbox( |
|
|
label="π
Start Date (YYYY-MM-DD format)", |
|
|
placeholder="YYYY-MM-DD (e.g., 2025-07-01)", |
|
|
value="2025-07-01" |
|
|
) |
|
|
|
|
|
end_date = gr.Textbox( |
|
|
label="π
End Date (YYYY-MM-DD format)", |
|
|
placeholder="YYYY-MM-DD (e.g., 2025-08-30)", |
|
|
value="2025-08-30" |
|
|
) |
|
|
|
|
|
num_lessons = gr.Slider( |
|
|
minimum=1, |
|
|
maximum=200, |
|
|
value=90, |
|
|
step=1, |
|
|
label="π Number of Lessons" |
|
|
) |
|
|
|
|
|
off_days = gr.CheckboxGroup( |
|
|
choices=["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], |
|
|
label="π« Off Days (Select days you don't want to study)", |
|
|
value=["Sunday"] |
|
|
) |
|
|
|
|
|
logo_file = gr.File( |
|
|
label="πΌοΈ Upload Logo (Optional - PNG, JPG supported)", |
|
|
file_types=["image"], |
|
|
type="binary" |
|
|
) |
|
|
|
|
|
generate_btn = gr.Button( |
|
|
"π Generate Schedule", |
|
|
variant="primary", |
|
|
size="lg", |
|
|
elem_classes=["generate-btn"] |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
gr.Markdown("### π Results") |
|
|
|
|
|
summary_output = gr.Markdown() |
|
|
|
|
|
schedule_table = gr.Dataframe( |
|
|
headers=["Date", "Day", "Session"], |
|
|
interactive=False, |
|
|
wrap=True |
|
|
) |
|
|
|
|
|
pdf_download = gr.File( |
|
|
label="π Download PDF Schedule", |
|
|
visible=False, |
|
|
interactive=True |
|
|
) |
|
|
|
|
|
|
|
|
generate_btn.click( |
|
|
fn=process_schedule, |
|
|
inputs=[start_date, end_date, off_days, num_lessons, logo_file], |
|
|
outputs=[summary_output, schedule_table, pdf_download, pdf_download] |
|
|
) |
|
|
|
|
|
|
|
|
gr.Markdown( |
|
|
""" |
|
|
### π‘ Usage Tips: |
|
|
|
|
|
**π
Date Format**: Always use YYYY-MM-DD format |
|
|
- β
Correct: `2025-07-01` |
|
|
- β Wrong: `07/01/2025` or `1 July 2025` |
|
|
|
|
|
**π« Off Days**: Select any days you want to skip |
|
|
- Example: Skip weekends β Select "Saturday" and "Sunday" |
|
|
- Example: Skip Friday prayers β Select "Friday" |
|
|
|
|
|
**π Lessons**: Adjust based on your course |
|
|
- Online course with 90 videos β Set to 90 |
|
|
- Textbook with 20 chapters β Set to 20 |
|
|
|
|
|
**πΌοΈ Logo**: Upload any image to brand your PDF |
|
|
- Supported: PNG, JPG, JPEG, GIF |
|
|
- Recommended size: 200x100 pixels or similar ratio |
|
|
|
|
|
### π― Example Scenarios: |
|
|
|
|
|
**Scenario 1: Weekend Study** |
|
|
- Start: 2025-07-01, End: 2025-12-31 |
|
|
- Off Days: Monday, Tuesday, Wednesday, Thursday, Friday |
|
|
- Lessons: 50 |
|
|
- Result: Weekend-only schedule |
|
|
|
|
|
**Scenario 2: Intensive Course** |
|
|
- Start: 2025-07-01, End: 2025-07-31 |
|
|
- Off Days: Sunday |
|
|
- Lessons: 90 |
|
|
- Result: ~3-4 lessons per day |
|
|
|
|
|
**Scenario 3: Relaxed Learning** |
|
|
- Start: 2025-07-01, End: 2025-12-31 |
|
|
- Off Days: Friday, Saturday, Sunday |
|
|
- Lessons: 100 |
|
|
- Result: ~1 lesson per weekday |
|
|
""" |
|
|
) |
|
|
|
|
|
return demo |
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
demo = create_interface() |
|
|
demo.launch( |
|
|
server_name="0.0.0.0", |
|
|
server_port=7860, |
|
|
share=False, |
|
|
debug=True |
|
|
) |