Phi Beta Sigma

State of Georgia Conference

Contact glmase78@gmail.com to request access

Phi Beta Sigma Georgia Shield

Phi Beta Sigma – State of Georgia Conference

Conference Management System  ·  Master Logistics Platform

👥User Management
🔑Permissions
📂Category Access
Task Controls
📊System Overview

⚙️ Admin Panel

Phi Beta Sigma – State of Georgia Conference Management System

👥 User Management

Create, edit, promote, demote, and deactivate user accounts. All changes apply immediately.

Add New User
👤 All Users 0

🔑 Permissions Management

Assign or remove specific operational permissions for each user. Select a user below to manage their permissions.

🎯 Select User to Edit Permissions

📂 Category Access Control

Control which sections each user can see and access. Admins automatically have access to all categories.

📋 User × Category Access Matrix

✅ Task Management Controls

Control who can mark tasks complete, add tasks, and delete tasks across the system.

🔒 Task Completion Policy
⭐ Master Admin

Can mark tasks complete, add tasks, delete tasks, add remarks

Full Access
🔑 Admin

Can mark tasks complete, add tasks, delete tasks, add remarks

Full Access
👤 User

Cannot mark tasks complete. Checkboxes are locked. Cannot add/delete tasks or save remarks.

Restricted
📊 Task Completion Stats
Loading...
⚙️ User-Level Task Override

Optionally grant a specific User-role account the ability to mark tasks complete (without promoting to Admin).

📊 System Overview Dashboard

Summary of all users, roles, access levels, and recent admin activity.

📋 All Users Summary
📝 Activity Log Recent admin actions

⭐ Master Admin Controls

Exclusive controls available only to the Master Admin. These actions have system-wide effects.

🛡️ Promote User to Admin

Grant Admin privileges to an existing User. They will gain full data management access and can manage other users.

🚫 Remove Admin Privileges

Demote an Admin back to a regular User. The Master Admin cannot be demoted.

🔄 System Reset Options
📤 Export System Data

Export the full user and permission database for backup or migration.

Active Plan

📦 Conference Archive Manager

View, switch between, or archive conference planning years. Archived years are read-only but fully preserved.

📊 Master Conference Overview

Auto-generated from all sections — click Refresh to update

📋 Master Logistics Summary

Click Refresh to generate the comprehensive logistics summary.
📝 Overview Notes
📎 Attachments

📋 Full Combined Conference Plan

All sections, notes, and data in one printable view

Click "Build Plan" to compile all sections into one comprehensive printable document.

🔐 Permissions & Users

Manage user roles, access, and category permissions

ROLES: ⭐ Master Admin 🔑 Admin 👤 User Click a row to expand permissions
Name Google Email Role Category Access Can Complete Tasks Last Active Actions

📁 Contract Repository

Centralized storage for all conference contracts and agreements

📝 Repository Notes

💻 Conference Virtual Session

Master Planning Checklist

👤 Chair Assignment

👥 Committee Members

NameChapterRolePhoneEmail
Completion0%
📝 Notes
📎 Attachments

📅 Friday Conference Master (Operations Day)

Day-of Checklist

Completion0%
📝 Notes
📎 Attachments

🎤 Opening Ceremony

Master Checklist

👤 Chair Assignment

👥 Committee Members

NameChapterRolePhoneEmail
Completion0%
📝 Notes
📎 Attachments

👑 Pageant

👤 Chair Assignment

👥 Committee Members

NameChapterRolePhoneEmail
Completion0%
📝 Notes
📎 Attachments

💃 Step Show

👤 Chair Assignment

👥 Committee Members

NameChapterRolePhoneEmail
Completion0%
📝 Notes
📎 Attachments

🍽️ 1914 Luncheon Management

👤 Chair Assignment

👥 Committee Members

NameChapterRolePhoneEmail
Completion0%
📝 Notes
📎 Attachments

📚 Workshop 1

👤 Chair Assignment

👥 Committee Members

NameChapterRolePhoneEmail
Completion0%
📝 Notes
📎 Attachments

📚 Workshop 2

👤 Chair Assignment

👥 Committee Members

NameChapterRolePhoneEmail
Completion0%
📝 Notes
📎 Attachments

📚 Workshop 3

👤 Chair Assignment

👥 Committee Members

NameChapterRolePhoneEmail
Completion0%
📝 Notes
📎 Attachments

🌸 Orchid Ball

👤 Chair Assignment

👥 Committee Members

NameChapterRolePhoneEmail
Completion0%
📝 Notes
📎 Attachments

🕊️ Necrology Session

👤 Chair Assignment

👥 Committee Members

NameChapterRolePhoneEmail
Completion0%
📝 Notes
📎 Attachments

🏛️ Local Chapters – Community Events

Events hosted by local chapters for conference attendees

Use the sections below to plan each local chapter event for conference attendees. These are community/social events and require their own planning details and insurance documentation.

✅ Local Chapters Tasks

🎉 Chapter Event 1

📌 Event Identity

📍 Where, When & Who

🎯 What & How – Event Details

🛡️ Insurance & Safety

💰 Budget

📝 Additional Notes – Event 1
📎 Attachments

🎊 Chapter Event 2

📌 Event Identity

📍 Where, When & Who

🎯 What & How – Event Details

🛡️ Insurance & Safety

💰 Budget

📝 Additional Notes – Event 2
📎 Attachments

🥳 Chapter Event 3

📌 Event Identity

📍 Where, When & Who

🎯 What & How – Event Details

🛡️ Insurance & Safety

💰 Budget

📝 Additional Notes – Event 3
📎 Attachments
📝 Local Chapters — General Notes

✉️ Greetings & Dignitary Letters

📝 Notes
📎 Attachments

🛍️ Vendors & Exhibitors

Vendor NameCategoryContact NamePhone / EmailContract SignedSetup Date/TimeFee $StatusNotes
📝 Vendor Notes
📎 Attachments

👥 Volunteers & Volunteer Management

NameRole/AssignmentPhoneEmailShiftStatus
📝 Notes
📎 Attachments

🎖️ Dignitary Management

NameTitle/OrganizationRole at EventSeatingEscortSpecial Needs
📝 Notes
📎 Attachments

🛡️ Insurance

Policy Details

Insurance Checklist

    📝 Notes
    📎 Attachments

    🗺️ Tour Convention Center

    📝 Notes
    📎 Attachments

    📋 Convention Center POCs

    Points of Contact – Event Planners & Staff

    NameTitle / RoleDepartmentPhoneEmailAvailabilityNotes
    📝 Notes
    📎 Attachments

    🗂️ Planning Committee POCs

    Points of Contact for all planning committee members

    NameTitle / RoleCommitteePhoneEmailAvailabilityResponsibilitiesNotes
    📝 Notes
    📎 Attachments

    ❤️ Social Action

    Community engagement and social action initiatives

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail

    ✅ Social Action Tasks

    • Remarks:

    ✉️ Greetings & Dignitary Letters

    From (Name)Title / OrganizationTypeDate RequestedDate ReceivedStatus

    ✅ Dignitary & Greetings Tasks

    📝 Notes
    📎 Attachments

    💰 Budget

    Income, expenses and financial tracking

    Income

    CategoryDescriptionEstimatedActualNotes

    Expenses

    CategoryDescriptionEstimatedActualVendor/PayeeStatusNotes
    📝 Budget Notes
    📎 Attachments

    🤝 Sponsors

    Sponsorship tracking and management

    OrganizationContact NamePhoneEmailLevelAmount $Benefits DeliveredStatusNotes
    📝 Sponsorship Notes
    📎 Attachments

    🏪 Exhibitors

    Booth management and assignments

    OrganizationContactPhone/EmailBooth #Category/TypeFee $Setup TimeInsurance ✓StatusNotes
    📝 Exhibitor Notes
    📎 Attachments

    📅 Meetings

    Conference planning meetings calendar

    📆 Calendar View

    Month Year

    📋 All Meetings

    📝 Meeting Notes
    📎 Attachments

    📚 Resources

    Documents, links, references, and materials for the conference

    📝 Resource Notes
    📎 Resource Attachments

    📋 Conference Agenda

    Multiple agendas for each conference day and event

    📝 Agenda Notes
    📎 Agenda Attachments

    🏨 Host Hotel & Overflow Hotels

    Hotel room blocks, rates, and accommodation details

    📝 Hotel Notes
    📎 Hotel Attachments

    👑 State Director

    State Leadership

    👤 Leadership Contact

    Progress
    0% 0 / 10

    ✅ Tasks

    📝 Notes
    📎 Attachments

    🥇 State 1st Vice Director

    State Leadership

    👤 Leadership Contact

    Progress
    0% 0 / 10

    ✅ Tasks

    📝 Notes
    📎 Attachments

    📋 State Chief of Staff

    State Leadership

    👤 Leadership Contact

    Progress
    0% 0 / 10

    ✅ Tasks

    📝 Notes
    📎 Attachments

    ⭐ Conference / Logistics Chair

    Conference Executive Staff

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    💰 State Treasurer

    Conference Executive Staff

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    🏗️ Logistics & Facilities Chair

    Executive Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    📋 Registration & Credentials Chair

    Operations Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    🎙️ Technology & Audio/Visual Chair

    Operations Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    🛡️ Security & Risk Management Chair

    Operations Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    ⚖️ Sergeant-at-Arms Chair

    Operations Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    🗳️ Elections, Nominations & Compliance Chair

    Operations Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    👥 Volunteer Coordinator Chair

    Operations Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    📚 Program & Workshops Chair

    Program & Events Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    🎓 OSSR Chair

    Program & Events Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    🎖️ VIP & Protocol Chair

    Program & Events Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    🏆 Awards & Recognition Chair

    Program & Events Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    🎉 Social Events Chair

    Program & Events Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    🕊️ Necrology Chair

    Program & Events Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    📣 Publicity & Communications Chair

    Communications & Marketing Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    📸 Social Media & Photography Chair

    Communications & Marketing Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    📰 Souvenir Journal Chair

    Communications & Marketing Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    🏛️ Chapter Relations Chair

    Communications & Marketing Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    🤝 Sponsorship & Fundraising Chair

    Sponsorship & Revenue Development Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    🏪 Vendor & Exhibitor Chair

    Sponsorship & Revenue Development Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    ❤️ Community Service Chair

    Service & Community Engagement Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    🏥 Medical Team Chair

    Service & Community Engagement Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    🏨 Hospitality Chair

    Hospitality & Conference Experience Committee

    👤 Chair Assignment

    👥 Committee Members

    NameChapterRolePhoneEmail
    Progress
    0% 0 / 10

    ✅ Tasks

    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:
    • Remarks:

    💰 Budget

    📝 Notes & Updates
    📎 Attachments

    ⏰ Pending Actions — Next 14 Days

    All outstanding tasks due within the next two weeks

    Overdue
    Due Soon
    Total Outstanding
    Next 14 Days

    Click 🔄 Refresh to load tasks due in the next 14 days.

    🖨️ Print Center

    Select task lists to preview and print

    0 selected  ·  0 tasks
    How to use: Check boxes next to lists, then click 🖨️ Print Selected or 💾 Save PDF. Click 👁 next to any list to preview it first.
    '; } // ── RENDER ALL REMARKS LISTS (for static chair-tab HTML on load) ── function renderAllRemarksLists() { document.querySelectorAll('.remarks-history[id^="rh-"]').forEach(function(el) { var key = el.id.replace('rh-', ''); try { renderRemarksList(key); } catch(e) {} }); } // ===================================================== // LOCAL CHAPTERS — DYNAMIC EVENT CARDS // ===================================================== // Track how many chapter events currently exist (starts at 3: ce1, ce2, ce3) function getChapterEventCount() { if (typeof state.chapterEventCount === 'number') return state.chapterEventCount; state.chapterEventCount = 3; return 3; } function fmtField(parentId, label, fieldId, type, placeholder) { var wrap = document.createElement('div'); wrap.className = 'form-group'; var lbl = document.createElement('label'); lbl.textContent = label; var input; if (type === 'textarea') { input = document.createElement('textarea'); } else if (type === 'select-liquor') { input = document.createElement('select'); ['N/A','Required','Obtained','Pending'].forEach(function(opt){ var o = document.createElement('option'); o.textContent = opt; input.appendChild(o); }); } else { input = document.createElement('input'); input.type = type || 'text'; } input.id = fieldId; if (placeholder && type !== 'select-liquor') input.placeholder = placeholder; wrap.appendChild(lbl); wrap.appendChild(input); return wrap; } function fmtRow(fields) { var row = document.createElement('div'); row.className = 'form-row'; fields.forEach(function(f){ row.appendChild(f); }); return row; } function buildChapterEventCard(n) { var card = document.createElement('div'); card.className = 'chapter-event-card'; card.id = 'chapter-event-' + n; // Header var header = document.createElement('div'); header.className = 'chapter-event-header'; var h3 = document.createElement('h3'); h3.textContent = '🎉 Chapter Event ' + n; var btnWrap = document.createElement('div'); btnWrap.style.cssText = 'display:flex;gap:8px'; var printBtn = document.createElement('button'); printBtn.className = 'btn btn-sm'; printBtn.style.cssText = 'background:rgba(255,255,255,.2);color:#fff;border:1px solid rgba(255,255,255,.4)'; printBtn.textContent = '🖨 Print'; printBtn.onclick = function(){ printChapterEvent(n); }; var delBtn = document.createElement('button'); delBtn.className = 'btn btn-sm no-print'; delBtn.style.cssText = 'background:rgba(185,28,28,.25);color:#fff;border:1px solid rgba(255,255,255,.4)'; delBtn.textContent = '🗑 Delete'; delBtn.onclick = function(){ deleteChapterEventCard(n); }; btnWrap.appendChild(printBtn); btnWrap.appendChild(delBtn); header.appendChild(h3); header.appendChild(btnWrap); card.appendChild(header); var body = document.createElement('div'); body.className = 'chapter-event-body'; // Section: Event Identity var s1 = document.createElement('div'); s1.className = 'chapter-event-section'; var s1h = document.createElement('h4'); s1h.textContent = '📌 Event Identity'; s1.appendChild(s1h); s1.appendChild(fmtRow([ fmtField(null,'Event Name / Title','ce'+n+'-name','text','Name of the event'), fmtField(null,'Hosting Chapter(s)','ce'+n+'-chapter','text','Chapter name(s)') ])); var whyField = fmtField(null,'Event Purpose / Why This Event','ce'+n+'-why','textarea','Describe the purpose, significance, and goals of this event for attendees...'); whyField.style.marginBottom = '10px'; s1.appendChild(whyField); body.appendChild(s1); // Section: Where, When & Who var s2 = document.createElement('div'); s2.className = 'chapter-event-section'; var s2h = document.createElement('h4'); s2h.textContent = '📍 Where, When & Who'; s2.appendChild(s2h); s2.appendChild(fmtRow([ fmtField(null,'Venue / Location','ce'+n+'-venue','text','Venue name and address'), fmtField(null,'Date','ce'+n+'-date','text','MM/DD/YYYY'), fmtField(null,'Time','ce'+n+'-time','text','Start \u2013 End time') ])); s2.appendChild(fmtRow([ fmtField(null,'Expected Attendance','ce'+n+'-attendance','text','Estimated # of guests'), fmtField(null,'Target Audience','ce'+n+'-audience','text','Who is this event for?'), fmtField(null,'Dress Code','ce'+n+'-dress','text','Attire requirements') ])); s2.appendChild(fmtRow([ fmtField(null,'Event Coordinator','ce'+n+'-coord','text','Name'), fmtField(null,'Coordinator Phone','ce'+n+'-phone','text','Phone'), fmtField(null,'Coordinator Email','ce'+n+'-email','email','Email') ])); body.appendChild(s2); // Section: What & How var s3 = document.createElement('div'); s3.className = 'chapter-event-section'; var s3h = document.createElement('h4'); s3h.textContent = '🎯 What & How \u2013 Event Details'; s3.appendChild(s3h); var whatField = fmtField(null,"Event Description / What's Happening",'ce'+n+'-what','textarea','Describe the event format, activities, entertainment, programming...'); whatField.style.marginBottom='10px'; s3.appendChild(whatField); s3.appendChild(fmtRow([ fmtField(null,'Ticket/Entry Price','ce'+n+'-price','text','Cost per person or free'), fmtField(null,'Ticket Sales Link/Contact','ce'+n+'-tickets','text','URL or contact info') ])); var entField = fmtField(null,'Entertainment / Programming','ce'+n+'-entertainment','textarea','DJ, band, performers, activities, food, drinks, etc...'); entField.style.marginBottom='10px'; s3.appendChild(entField); var logField = fmtField(null,'Logistics & Setup Plan','ce'+n+'-logistics','textarea','Describe setup, breakdown, staffing, equipment needs, vendor requirements...'); logField.style.marginBottom='10px'; s3.appendChild(logField); s3.appendChild(fmtField(null,'Transportation / Parking Info','ce'+n+'-transport','textarea','Shuttle service, parking details, directions for attendees...')); body.appendChild(s3); // Section: Insurance & Safety var s4 = document.createElement('div'); s4.className = 'chapter-event-section'; var s4h = document.createElement('h4'); s4h.textContent = '🛡️ Insurance & Safety'; s4.appendChild(s4h); s4.appendChild(fmtRow([ fmtField(null,'Insurance Provider','ce'+n+'-ins-provider','text','Insurance company'), fmtField(null,'Policy Number','ce'+n+'-ins-policy','text','Policy #'), fmtField(null,'Coverage Amount','ce'+n+'-ins-amount','text','$0.00') ])); s4.appendChild(fmtRow([ fmtField(null,'Liquor Liability','ce'+n+'-liquor','select-liquor',null), fmtField(null,'Security / Safety Plan','ce'+n+'-security','text','Security provider or plan'), fmtField(null,'Emergency Contact On-Site','ce'+n+'-emergency','text','Name and phone') ])); s4.appendChild(fmtField(null,'Special Permits / Requirements','ce'+n+'-permits','textarea','Noise permits, venue requirements, alcohol permits, etc...')); body.appendChild(s4); // Section: Budget var s5 = document.createElement('div'); s5.className = 'chapter-event-section'; var s5h = document.createElement('h4'); s5h.textContent = '💰 Budget'; s5.appendChild(s5h); s5.appendChild(fmtRow([ fmtField(null,'Total Budget','ce'+n+'-budget','text','$0.00'), fmtField(null,'Expected Revenue','ce'+n+'-revenue','text','$0.00'), fmtField(null,'Sponsorships','ce'+n+'-sponsors','text','Sponsors and amounts') ])); body.appendChild(s5); // Notes var notesDiv = document.createElement('div'); notesDiv.className = 'notes-section'; var notesHdr = document.createElement('div'); notesHdr.className = 'notes-header'; notesHdr.textContent = '📝 Additional Notes \u2013 Event ' + n; var notesTA = document.createElement('textarea'); notesTA.id = 'notes-ce' + n; notesTA.placeholder = 'Additional planning notes...'; notesDiv.appendChild(notesHdr); notesDiv.appendChild(notesTA); body.appendChild(notesDiv); // Attachments var attDiv = document.createElement('div'); attDiv.className = 'attachments-section'; attDiv.style.marginTop = '12px'; var attHdr = document.createElement('div'); attHdr.className = 'attachments-header'; var attSpan = document.createElement('span'); attSpan.textContent = '📎 Attachments'; var attLbl = document.createElement('label'); attLbl.className = 'btn btn-outline btn-sm'; attLbl.style.cssText = 'color:var(--royal);border-color:var(--royal);cursor:pointer'; attLbl.textContent = '+ Add File'; var attFile = document.createElement('input'); attFile.type = 'file'; attFile.multiple = true; attFile.style.display = 'none'; attFile.onchange = function(){ addAttachment(this, 'ce'+n); }; attLbl.appendChild(attFile); attHdr.appendChild(attSpan); attHdr.appendChild(attLbl); var attList = document.createElement('div'); attList.className = 'attachment-list'; attList.id = 'attach-ce' + n; attDiv.appendChild(attHdr); attDiv.appendChild(attList); body.appendChild(attDiv); card.appendChild(body); return card; } function addChapterEventCard() { var count = getChapterEventCount(); var n = count + 1; var card = buildChapterEventCard(n); // Insert before the "Local Chapters — General Notes" section var generalNotes = document.getElementById('notes-chapters'); var insertTarget = generalNotes ? generalNotes.closest('.notes-section') : null; var tabPanel = document.getElementById('tab-chapters'); if (insertTarget && tabPanel) { tabPanel.insertBefore(card, insertTarget); } else if (tabPanel) { tabPanel.appendChild(card); } state.chapterEventCount = n; try { saveCurrentYear(); } catch(e) {} showToast('Chapter Event ' + n + ' added!'); setTimeout(function(){ card.scrollIntoView({behavior:'smooth', block:'start'}); }, 150); } function deleteChapterEventCard(n) { confirmDelete('Delete Chapter Event ' + n + '?', 'This removes all details entered for this event. This cannot be undone.', function(){ var card = document.getElementById('chapter-event-' + n); if (card) card.remove(); try { saveCurrentYear(); } catch(e) {} showToast('Chapter Event ' + n + ' deleted.'); }); } // Restore dynamically-added chapter event cards from saved state function restoreChapterEventCards() { var count = getChapterEventCount(); if (count <= 3) return; // ce1-3 already exist in static HTML for (var n = 4; n <= count; n++) { if (!document.getElementById('chapter-event-' + n)) { var card = buildChapterEventCard(n); var generalNotes = document.getElementById('notes-chapters'); var insertTarget = generalNotes ? generalNotes.closest('.notes-section') : null; var tabPanel = document.getElementById('tab-chapters'); if (insertTarget && tabPanel) tabPanel.insertBefore(card, insertTarget); else if (tabPanel) tabPanel.appendChild(card); } } } // ===================================================== // COMMITTEE MEMBERS — add/remove roster per tab // ===================================================== function addCommitteeMember(tabId) { if (!state.committeeMembers) state.committeeMembers = {}; if (!state.committeeMembers[tabId]) state.committeeMembers[tabId] = []; state.committeeMembers[tabId].push({ name: '', chapter: '', role: '', phone: '', email: '' }); renderCommitteeMembers(tabId); try { saveCurrentYear(); } catch(e) {} // Focus the new row's name field setTimeout(function() { var rows = document.querySelectorAll('#members-tbody-' + tabId + ' input[data-field="name"]'); if (rows.length) rows[rows.length - 1].focus(); }, 50); } function removeCommitteeMember(tabId, idx) { var member = state.committeeMembers && state.committeeMembers[tabId] && state.committeeMembers[tabId][idx]; var name = member && member.name ? member.name : 'this member'; confirmDelete('Remove ' + name + '?', 'This removes them from the committee member list.', function() { state.committeeMembers[tabId].splice(idx, 1); renderCommitteeMembers(tabId); try { saveCurrentYear(); } catch(e) {} }); } function updateCommitteeMemberField(tabId, idx, field, value) { if (!state.committeeMembers || !state.committeeMembers[tabId] || !state.committeeMembers[tabId][idx]) return; state.committeeMembers[tabId][idx][field] = value; try { saveCurrentYear(); } catch(e) {} } function renderCommitteeMembers(tabId) { var tbody = document.getElementById('members-tbody-' + tabId); if (!tbody) return; var members = (state.committeeMembers && state.committeeMembers[tabId]) ? state.committeeMembers[tabId] : []; if (!members.length) { tbody.innerHTML = 'No committee members added yet.'; return; } tbody.innerHTML = ''; members.forEach(function(m, idx) { var tr = document.createElement('tr'); var fields = ['name', 'chapter', 'role', 'phone', 'email']; var placeholders = { name: 'Full name', chapter: 'Home chapter', role: 'e.g. Logistics, Setup, Greeter', phone: '(000) 000-0000', email: 'Email address' }; fields.forEach(function(f) { var td = document.createElement('td'); var input = document.createElement('input'); input.type = 'text'; input.value = m[f] || ''; input.placeholder = placeholders[f]; input.setAttribute('data-field', f); input.style.cssText = 'width:100%;border:1px solid transparent;background:transparent;padding:4px 6px;font-size:.85rem;color:var(--royal-dark);border-radius:4px'; input.onfocus = function(){ this.style.borderColor = 'rgba(26,82,196,.3)'; this.style.background = '#f8faff'; }; input.onblur = function(){ this.style.borderColor = 'transparent'; this.style.background = 'transparent'; }; (function(ti, i, fld) { input.addEventListener('change', function() { updateCommitteeMemberField(ti, i, fld, this.value); }); })(tabId, idx, f); td.appendChild(input); tr.appendChild(td); }); var actionTd = document.createElement('td'); actionTd.className = 'no-print'; var delBtn = document.createElement('button'); delBtn.className = 'btn btn-danger btn-sm'; delBtn.textContent = '🗑'; delBtn.title = 'Remove member'; (function(ti, i) { delBtn.onclick = function() { removeCommitteeMember(ti, i); }; })(tabId, idx); actionTd.appendChild(delBtn); tr.appendChild(actionTd); tbody.appendChild(tr); }); } // Render committee member tables for every tab that has one (called on load) var COMMITTEE_MEMBER_TABS = [ 'virtual','opening','workshop1','workshop2','workshop3','orchid','necrology','pageant','stepshow','luncheon', 'socialaction','confchair','financechair','logisticschair','regchair','techchair', 'securitychair','sarmschair','electionschair','volcoordchair','progchair','ossrchair', 'vipchair','awardschair','socialchair','necrochair','publicitychair','socialmediachair', 'journalchair','chaprelchair','sponsfundchair','vendexhibchair','commservchair', 'medicalchair','hospchair' ]; function renderAllCommitteeMembers() { COMMITTEE_MEMBER_TABS.forEach(function(tid) { try { renderCommitteeMembers(tid); } catch(e) {} }); } // ── RESTORE DUE DATES AND ASSIGNED-TO FIELDS INTO DOM ── // Called after loadYear() restores state.dueDates and state.assigned // Needed for static HTML chair/leadership tabs whose date inputs aren't re-rendered on load function restoreDueDatesAndAssigned() { // Restore all due-* inputs if (state.dueDates) { Object.keys(state.dueDates).forEach(function(key) { var el = document.getElementById('due-' + key); if (el && state.dueDates[key]) { el.value = state.dueDates[key]; // Apply color coding (overdue = red, due soon = yellow) try { pbsRefreshDueColors(el, key); } catch(e) {} } }); } // Restore all asgn-* inputs if (state.assigned) { Object.keys(state.assigned).forEach(function(key) { var el = document.getElementById('asgn-' + key); if (el && state.assigned[key]) { el.value = state.assigned[key]; } }); } } // ===================================================== // CHAIR-TAB TASK PERSISTENCE — save & restore // ===================================================== // Save a newly-added chair task record to state so it survives reload function saveChairTaskToState(chairId, key, text) { if (!state.chairAddedTasks) state.chairAddedTasks = {}; if (!state.chairAddedTasks[chairId]) state.chairAddedTasks[chairId] = []; // Avoid duplicates var exists = state.chairAddedTasks[chairId].some(function(t){ return t.key === key; }); if (!exists) state.chairAddedTasks[chairId].push({ key: key, text: text }); } function removeChairTaskFromState(chairId, key) { if (!state.chairAddedTasks || !state.chairAddedTasks[chairId]) return; state.chairAddedTasks[chairId] = state.chairAddedTasks[chairId].filter(function(t){ return t.key !== key; }); delete state.checkboxes[key]; delete state.dueDates[key]; delete state.assigned[key]; delete state.remarksList[key]; } // Rebuild all dynamically-added chair tasks from saved state on page load function restoreChairAddedTasks() { if (!state.chairAddedTasks) return; Object.keys(state.chairAddedTasks).forEach(function(chairId) { var tasks = state.chairAddedTasks[chairId]; if (!tasks || !tasks.length) return; tasks.forEach(function(t) { // Don't add if key already exists in the DOM (avoid duplicates) if (document.getElementById('cb-' + t.key)) return; buildChairTaskLi(chairId, t.key, t.text, true); }); }); // After rebuilding DOM, restore due dates and remarks into those new elements try { restoreDueDatesAndAssigned(); } catch(e) {} try { renderAllRemarksLists(); } catch(e) {} } // Build a chair task
  • and insert it into the list // If fromRestore=true, we're rebuilding from saved state (don't save again) function buildChairTaskLi(chairId, key, text, fromRestore) { var list = document.getElementById('sections-' + chairId); if (!list) return; var addRow = document.getElementById('add-task-row-' + chairId); var cb = document.createElement('input'); cb.type = 'checkbox'; cb.id = 'cb-' + key; cb.setAttribute('data-chairid', chairId); cb.setAttribute('data-key', key); if (state.checkboxes && state.checkboxes[key]) cb.checked = true; cb.addEventListener('change', function() { state.checkboxes[this.getAttribute('data-key')] = this.checked; updateChairProgress(this.getAttribute('data-chairid')); var k = this.getAttribute('data-key'); var dEl = document.getElementById('due-' + k); if (dEl) pbsRefreshDueColors(dEl, k); try { saveCurrentYear(); } catch(e) {} try { refreshOverview(); } catch(e) {} }); var lbl = document.createElement('label'); lbl.htmlFor = 'cb-' + key; lbl.textContent = text; var delBtn = document.createElement('button'); delBtn.className = 'delete-task-btn no-print'; delBtn.textContent = '🗑'; delBtn.setAttribute('data-chairid', chairId); delBtn.addEventListener('click', function() { var cid = this.getAttribute('data-chairid'); confirmDelete('Delete task?', '"' + text + '"', function() { delBtn.closest('li').remove(); removeChairTaskFromState(cid, key); updateChairProgress(cid); try { saveCurrentYear(); } catch(e) {} }); }); var topDiv = document.createElement('div'); topDiv.className = 'check-item-top'; topDiv.appendChild(cb); topDiv.appendChild(lbl); topDiv.appendChild(delBtn); // Due Date + Assigned To var meta = document.createElement('div'); meta.style.cssText = 'display:flex;align-items:center;gap:14px;padding:4px 0 4px 28px;flex-wrap:wrap'; var dueW = document.createElement('div'); dueW.style.cssText = 'display:flex;align-items:center;gap:6px'; var dueL = document.createElement('label'); dueL.style.cssText = "font-family:'Share Tech Mono',monospace;font-size:.63rem;color:var(--mid);text-transform:uppercase;letter-spacing:.06em;white-space:nowrap"; dueL.textContent = 'Due Date'; var dueI = document.createElement('input'); dueI.type = 'date'; dueI.id = 'due-' + key; dueI.style.cssText = 'border:1px solid rgba(26,82,196,.2);border-radius:4px;padding:2px 7px;font-size:.78rem;color:var(--royal-dark);background:#f8faff'; dueI.setAttribute('data-key', key); if (state.dueDates && state.dueDates[key]) { dueI.value = state.dueDates[key]; try { pbsRefreshDueColors(dueI, key); } catch(e) {} } dueI.addEventListener('change', function() { var k = this.getAttribute('data-key'); state.dueDates[k] = this.value; pbsRefreshDueColors(this, k); try { saveCurrentYear(); } catch(e) {} }); dueW.appendChild(dueL); dueW.appendChild(dueI); var asgnW = document.createElement('div'); asgnW.style.cssText = 'display:flex;align-items:center;gap:6px'; var asgnL = document.createElement('label'); asgnL.style.cssText = "font-family:'Share Tech Mono',monospace;font-size:.63rem;color:var(--mid);text-transform:uppercase;letter-spacing:.06em;white-space:nowrap"; asgnL.textContent = 'Assigned To'; var asgnI = document.createElement('input'); asgnI.type = 'text'; asgnI.id = 'asgn-' + key; asgnI.placeholder = 'Name'; asgnI.style.cssText = 'border:1px solid rgba(26,82,196,.2);border-radius:4px;padding:2px 8px;font-size:.78rem;color:var(--royal-dark);background:#f8faff;width:120px'; asgnI.setAttribute('data-key', key); if (state.assigned && state.assigned[key]) asgnI.value = state.assigned[key]; asgnI.addEventListener('change', function() { state.assigned[this.getAttribute('data-key')] = this.value; try { saveCurrentYear(); } catch(e) {} }); asgnW.appendChild(asgnL); asgnW.appendChild(asgnI); meta.appendChild(dueW); meta.appendChild(asgnW); // Remarks var rhDiv = document.createElement('div'); rhDiv.className = 'remarks-history'; rhDiv.id = 'rh-' + key; var remLbl = document.createElement('span'); remLbl.className = 'remarks-label'; remLbl.textContent = 'Remarks:'; var remInput = document.createElement('input'); remInput.type = 'text'; remInput.placeholder = 'Add remark and press Save...'; remInput.setAttribute('data-rkey', key); remInput.setAttribute('data-chairid', chairId); remInput.addEventListener('keydown', function(e) { if (e.key === 'Enter') saveRemark(this.getAttribute('data-rkey'), this.getAttribute('data-chairid')); }); var remBtn = document.createElement('button'); remBtn.textContent = 'Save'; remBtn.setAttribute('data-rkey', key); remBtn.setAttribute('data-chairid', chairId); remBtn.addEventListener('click', function() { saveRemark(this.getAttribute('data-rkey'), this.getAttribute('data-chairid')); }); var addRowDiv = document.createElement('div'); addRowDiv.className = 'remarks-add-row'; addRowDiv.appendChild(remLbl); addRowDiv.appendChild(remInput); addRowDiv.appendChild(remBtn); var li = document.createElement('li'); li.className = 'check-item'; li.appendChild(topDiv); li.appendChild(meta); li.appendChild(rhDiv); li.appendChild(addRowDiv); if (addRow) list.insertBefore(li, addRow); else list.appendChild(li); } // ===================================================== // PENDING ACTIONS SEED v2 — Jun 24 2026 import // ===================================================== function seedPendingActionsV2() { // These are the tasks from the Jun 24 2026 Pending Actions report var ENRICH_TASKS = [ { "type": "enrich", "key": "orchid-0-3", "tabId": "orchid", "text": "Milestone 1: Identify planning committee & assign roles", "dueDate": "2026-06-24", "assigned": "Brandon Brimm", "remark": "Bro. Brimm will be sending a list of committee mbrs by Monday, June 22, 2026. the Co-Chair will be from the Valdosta Alumni Chapter.", "remarkTime": "Jun 20, 10:22 AM" } ]; var CHAIR_TASKS = [ { "type": "chair", "chairId": "statedir", "key": "statedir-custom-s1", "text": "Chair / Co-Chair Names", "dueDate": "2026-06-24", "assigned": "Dr. Stephens", "remark": "Chair and Co-Chair for the 1914 Luncheon (Finalize)", "remarkTime": "Jun 24, 12:14 PM" }, { "type": "chair", "chairId": "statedir", "key": "statedir-custom-s2", "text": "Second Vice Presidents", "dueDate": "2026-06-24", "assigned": "Dr. Stephens", "remark": "Dr. Stephens will provide a list of candidates for the 2nd Vice President's for the State. They will have the first opportunity to volunteer for committees of their choice.", "remarkTime": "Jun 24, 12:14 PM" }, { "type": "chair", "chairId": "statedir", "key": "statedir-custom-s3", "text": "Marketing Plan for Chapter Sponsors", "dueDate": "2026-06-24", "assigned": "Dr. Stephens", "remark": "Dr. Stephens will review/revise the marketing strategy for the chapter sponsor program.", "remarkTime": "Jun 24, 12:16 PM" }, { "type": "chair", "chairId": "statedir", "key": "statedir-custom-s4", "text": "Marketing Plan for Sigma Sponsors", "dueDate": "2026-06-24", "assigned": "Dr. Stephens", "remark": "Dr. Stephens will review/revise the marketing strategy for the Sigma Scholar sponsorships.", "remarkTime": "Jun 24, 12:18 PM" }, { "type": "chair", "chairId": "statecos", "key": "statecos-custom-s1", "text": "Committee List Review", "dueDate": "2026-06-24", "assigned": "Dr. Hall", "remark": "Review / Revise the committee list for Chairs/Co-Chairs", "remarkTime": "Jun 24, 12:27 PM" }, { "type": "chair", "chairId": "volcoordchair", "key": "volcoordchair-custom-s1", "text": "Recommend Volunteer Platform", "dueDate": "2026-06-24", "assigned": "Bro. Mosley", "remark": "Research and provide recommendations as to what platform we will use for volunteer sign up.", "remarkTime": "Jun 24, 12:32 PM" } ]; // Mark due dates, assigned, and remarks in state for built-in checklist items ENRICH_TASKS.forEach(function(t) { if (!state.dueDates[t.key]) state.dueDates[t.key] = t.dueDate; if (!state.assigned[t.key]) state.assigned[t.key] = t.assigned; if (!state.remarksList[t.key] || !state.remarksList[t.key].length) { state.remarksList[t.key] = [{ text: t.remark, time: t.remarkTime }]; } }); // Add custom tasks to chair tabs if (!state.chairAddedTasks) state.chairAddedTasks = {}; CHAIR_TASKS.forEach(function(t) { if (!state.chairAddedTasks[t.chairId]) state.chairAddedTasks[t.chairId] = []; var exists = state.chairAddedTasks[t.chairId].some(function(x){ return x.key === t.key; }); if (!exists) { state.chairAddedTasks[t.chairId].push({ key: t.key, text: t.text }); } if (!state.dueDates[t.key]) state.dueDates[t.key] = t.dueDate; if (!state.assigned[t.key]) state.assigned[t.key] = t.assigned; if (!state.remarksList[t.key] || !state.remarksList[t.key].length) { state.remarksList[t.key] = [{ text: t.remark, time: t.remarkTime }]; } }); try { saveCurrentYear(); } catch(e) {} } // ===================================================== // MULTI-YEAR CONFERENCE SYSTEM // ===================================================== var CONFERENCE_YEARS = ['2027','2028','2029','2030','2031','2032','2033','2034','2035']; var currentYear = '2027'; var conferenceData = {}; // keyed by year function getYearKey(year) { return 'pbs_conf_' + year; } function getArchiveKey(year) { return 'pbs_archive_' + year; } function loadAllYears() { CONFERENCE_YEARS.forEach(function(yr) { try { var saved = localStorage.getItem(getYearKey(yr)); if (saved) conferenceData[yr] = JSON.parse(saved); else conferenceData[yr] = null; } catch(e) { conferenceData[yr] = null; } }); } function saveCurrentYear() { // ── DOM SWEEP: capture ALL input values into state before saving ── // This ensures nothing is lost even if onchange handlers missed a field. try { // Sweep all due date inputs document.querySelectorAll('input[type="date"][id^="due-"]').forEach(function(el) { if (el.value) state.dueDates[el.id.replace('due-','')] = el.value; }); // Sweep all assigned-to inputs document.querySelectorAll('input[id^="asgn-"]').forEach(function(el) { if (el.value) state.assigned[el.id.replace('asgn-','')] = el.value; }); // Sweep all checkboxes document.querySelectorAll('input[type="checkbox"][id^="cb-"]').forEach(function(el) { state.checkboxes[el.id.replace('cb-','')] = el.checked; }); } catch(sweepErr) { console.warn('DOM sweep error:', sweepErr); } try { var payload = { year: currentYear, savedAt: new Date().toISOString(), state: JSON.parse(JSON.stringify(state)), formFields: collectFormFields(), resources: state.resources || [], agendas: state.agendas || [], hotels: state.hotels || [], notes: collectAllNotes(), archived: false }; try { var serialized = JSON.stringify(payload); localStorage.setItem(getYearKey(currentYear), serialized); // Also save to sessionStorage as backup try { sessionStorage.setItem('pbs_backup_' + currentYear, serialized); } catch(e2) {} } catch(quotaErr) { // If storage is full (often due to large attachments), save without attachment data console.warn('Storage quota exceeded, saving without attachment data:', quotaErr); var lightPayload = JSON.parse(JSON.stringify(payload)); if (lightPayload.state && lightPayload.state.attachments) { // Strip dataUrls but keep metadata (names, dates, sizes) Object.keys(lightPayload.state.attachments).forEach(function(tabId) { if (lightPayload.state.attachments[tabId]) { lightPayload.state.attachments[tabId] = lightPayload.state.attachments[tabId].map(function(a) { return { name: a.name, date: a.date, size: a.size, dataUrl: null }; }); } }); } localStorage.setItem(getYearKey(currentYear), JSON.stringify(lightPayload)); } conferenceData[currentYear] = payload; var el = document.getElementById('conf-last-saved'); if (el) { el.textContent = '✅ Saved ' + new Date().toLocaleTimeString(); el.style.color = '#1e7a4a'; } updateSaveIndicator && updateSaveIndicator(true); } catch(e) { console.warn('saveCurrentYear error:', e); var el = document.getElementById('conf-last-saved'); if (el) { el.textContent = '⚠️ Save failed — check storage'; el.style.color = '#b91c1c'; } } } function collectAllNotes() { var notes = {}; document.querySelectorAll('textarea[id^="notes-"]').forEach(function(el) { if (el.value && el.value.trim()) notes[el.id.replace('notes-','')] = el.value; }); return notes; } function loadYear(year) { try { var saved = localStorage.getItem(getYearKey(year)); if (!saved) return false; var data = JSON.parse(saved); if (!data) return false; // Load state if (data.state) { var s = data.state; if (s.checkboxes) Object.assign(state.checkboxes, s.checkboxes); if (s.remarksList) Object.assign(state.remarksList, s.remarksList); if (s.dueDates) Object.assign(state.dueDates, s.dueDates); if (s.deletedTasks) Object.assign(state.deletedTasks, s.deletedTasks); if (s.assigned) Object.assign(state.assigned, s.assigned); if (s.addedTasks) Object.assign(state.addedTasks, s.addedTasks); if (s.vendors && s.vendors.length) state.vendors = s.vendors; if (s.volunteers && s.volunteers.length) state.volunteers = s.volunteers; if (s.dignitaries && s.dignitaries.length) state.dignitaries = s.dignitaries; if (s.pocs && s.pocs.length) state.pocs = s.pocs; if (s.planningPocs && s.planningPocs.length) state.planningPocs = s.planningPocs; if (s.greetings && s.greetings.length) state.greetings = s.greetings; if (s.budgetIncome && s.budgetIncome.length) state.budgetIncome = s.budgetIncome; if (s.budgetExpense && s.budgetExpense.length) state.budgetExpense = s.budgetExpense; if (s.sponsors && s.sponsors.length) state.sponsors = s.sponsors; if (s.exhibitors && s.exhibitors.length) state.exhibitors = s.exhibitors; if (s.meetings && s.meetings.length) state.meetings = s.meetings; if (s.contracts && s.contracts.length) state.contracts = s.contracts; if (s.attachments) Object.assign(state.attachments, s.attachments); if (s.committeeMembers) Object.assign(state.committeeMembers, s.committeeMembers); if (s.chairAddedTasks) Object.assign(state.chairAddedTasks, s.chairAddedTasks); if (typeof s.chapterEventCount === 'number') state.chapterEventCount = s.chapterEventCount; } if (data.resources) state.resources = data.resources; if (data.agendas) state.agendas = data.agendas; if (data.hotels) state.hotels = data.hotels; // Restore notes if (data.notes) { Object.entries(data.notes).forEach(function(kv) { var el = document.getElementById('notes-' + kv[0]); if (el) el.value = kv[1]; }); } // Restore form fields if (data.formFields) { Object.entries(data.formFields).forEach(function(kv) { var el = document.getElementById(kv[0]); if (el && kv[1] !== undefined) el.value = kv[1]; }); } return data.archived || false; } catch(e) { console.warn('loadYear error:', e); return false; } } function switchConference(year) { // Save current year first saveCurrentYear(); currentYear = year; // Reset ALL state for year switch state.vendors=[]; state.volunteers=[]; state.dignitaries=[]; state.pocs=[]; state.planningPocs=[]; state.greetings=[]; state.budgetIncome=[]; state.budgetExpense=[]; state.sponsors=[]; state.exhibitors=[]; state.meetings=[]; state.contracts=[]; state.resources=[]; state.agendas=[]; state.hotels=[]; state.checkboxes={}; state.remarksList={}; state.dueDates={}; state.deletedTasks={}; state.assigned={}; state.addedTasks={}; state.chairAddedTasks={}; state.committeeMembers={}; state.chapterEventCount=3; // Clear all text inputs and selects on static chair tabs document.querySelectorAll('input[id$="-name"],input[id$="-chapter"],input[id$="-phone"],input[id$="-email"],input[id$="-cochair-name"],input[id$="-cochair-chapter"],input[id$="-cochair-phone"],input[id$="-cochair-email"]').forEach(function(el){ el.value=''; }); document.querySelectorAll('select[id$="-status"]').forEach(function(el){ el.selectedIndex=0; }); document.querySelectorAll('input[id^="due-"],input[id^="asgn-"]').forEach(function(el){ el.value=''; }); // Remove dynamically-added chair tasks from DOM document.querySelectorAll('.check-item[id^="ci-"]').forEach(function(el){ el.remove(); }); // Clear all notes document.querySelectorAll('textarea[id^="notes-"]').forEach(function(el){ el.value=''; }); // Re-render all sections Object.keys(TABS_DATA).forEach(function(tid){ try { if(TABS_DATA[tid]&&TABS_DATA[tid].sections) renderSections(tid,TABS_DATA[tid].sections); } catch(e){} }); try { renderInsuranceChecklist(); } catch(e){} try { renderVendors(); } catch(e){} try { renderVolunteers(); } catch(e){} try { renderDignitaries(); } catch(e){} try { renderPocs(); } catch(e){} try { renderPlanningPocs(); } catch(e){} try { renderGreetings(); } catch(e){} try { renderBudget(); } catch(e){} try { renderSponsors(); } catch(e){} try { renderExhibitors(); } catch(e){} try { renderMeetings(); } catch(e){} try { renderCalendar(); } catch(e){} try { renderContracts(); } catch(e){} try { renderResources(); } catch(e){} try { renderAgendas(); } catch(e){} try { renderHotels(); } catch(e){} // Load saved data for the new year var isArchived = loadYear(year); // Grab form fields from the saved data for restoration after render var _switchFormFields = null; try { var _sd = localStorage.getItem(getYearKey(year)); if (_sd) { var _pd = JSON.parse(_sd); if (_pd && _pd.formFields) _switchFormFields = _pd.formFields; } } catch(e) {} // Re-render with loaded data setTimeout(function(){ try { renderVendors(); } catch(e){} try { renderVolunteers(); } catch(e){} try { renderDignitaries(); } catch(e){} try { renderPocs(); } catch(e){} try { renderPlanningPocs(); } catch(e){} try { renderGreetings(); } catch(e){} try { renderBudget(); } catch(e){} try { renderSponsors(); } catch(e){} try { renderExhibitors(); } catch(e){} try { renderMeetings(); } catch(e){} try { renderCalendar(); } catch(e){} try { renderContracts(); } catch(e){} try { renderResources(); } catch(e){} try { renderAgendas(); } catch(e){} try { renderHotels(); } catch(e){} try { refreshOverview(); } catch(e){} // Re-render checklists with loaded checkboxes/remarks Object.keys(TABS_DATA).forEach(function(tid){ try { if(TABS_DATA[tid]&&TABS_DATA[tid].sections) renderSections(tid,TABS_DATA[tid].sections); } catch(e){} }); // Restore dynamically-added chair tasks, form fields, due dates, remarks, committee members try { restoreChairAddedTasks(); } catch(e){} try { if (_switchFormFields) restoreFormFields(_switchFormFields); } catch(e){} try { restoreDueDatesAndAssigned(); } catch(e){} try { renderAllRemarksLists(); } catch(e){} try { renderAllCommitteeMembers(); } catch(e){} }, 200); // Update UI updateConferenceUI(year, isArchived); showToast('Switched to ' + year + ' Conference' + (isArchived ? ' (Archived — Read Only)' : '')); } function updateConferenceUI(year, isArchived) { var sel = document.getElementById('conf-year-select'); if (sel) sel.value = year; var badge = document.getElementById('conf-status-badge'); if (badge) { badge.textContent = isArchived ? 'Archived (Read Only)' : 'Active Plan'; badge.className = 'conf-badge ' + (isArchived ? 'conf-badge-archive' : 'conf-badge-active'); } // Update title in header var h1 = document.querySelector('header .header-text h1'); if (h1) h1.textContent = 'Phi Beta Sigma \u2013 State of Georgia Conference ' + year; // Show archived notice in main var notice = document.getElementById('archived-notice'); if (!notice) { notice = document.createElement('div'); notice.id = 'archived-notice'; notice.className = 'conf-archived-notice no-print'; var main = document.querySelector('main'); if (main) main.insertBefore(notice, main.firstChild); } if (isArchived) { notice.style.display = 'flex'; notice.innerHTML = '📦' + year + ' Conference is Archived. This is a read-only view. Switch to an active year to make changes.'; } else { notice.style.display = 'none'; } } function startFreshPlan() { var year = document.getElementById('conf-year-select').value; if (!confirm('Start a FRESH plan for ' + year + '? This will clear all existing data for ' + year + '. This cannot be undone.\n\nTip: Archive first to preserve the current data.')) return; localStorage.removeItem(getYearKey(year)); switchConference(year); showToast('Fresh plan started for ' + year + '!'); } function archiveCurrentYear() { var year = currentYear; if (!confirm('Archive the ' + year + ' conference plan? It will become read-only but fully preserved and viewable at any time.')) return; saveCurrentYear(); try { var saved = localStorage.getItem(getYearKey(year)); if (saved) { var data = JSON.parse(saved); data.archived = true; data.archivedAt = new Date().toISOString(); localStorage.setItem(getYearKey(year), JSON.stringify(data)); conferenceData[year] = data; } } catch(e) {} updateConferenceUI(year, true); renderArchiveList(); showToast(year + ' conference has been archived!'); } function openArchiveModal() { renderArchiveList(); document.getElementById('archive-modal').classList.add('open'); } function closeArchiveModal() { document.getElementById('archive-modal').classList.remove('open'); } function renderArchiveList() { var list = document.getElementById('archive-conf-list'); if (!list) return; loadAllYears(); list.innerHTML = ''; CONFERENCE_YEARS.forEach(function(yr) { var data = conferenceData[yr]; var hasData = !!data; var isArchived = hasData && data.archived; var isCurrent = yr === currentYear; var div = document.createElement('div'); div.className = 'conf-list-item'; div.innerHTML = '' + yr + '' + '' + (isArchived ? '📦 Archived' : hasData ? '✏️ Active Plan' : '⬜ No Data') + (isCurrent ? ' (Current)' : '') + '' + '
    ' + (hasData && isArchived ? '' : '') + (!isCurrent && hasData ? '' : '') + (hasData ? '' : '') + '
    '; list.appendChild(div); }); } function unarchiveYear(year) { try { var saved = localStorage.getItem(getYearKey(year)); if (saved) { var data = JSON.parse(saved); data.archived = false; delete data.archivedAt; localStorage.setItem(getYearKey(year), JSON.stringify(data)); conferenceData[year] = data; } } catch(e) {} if (year === currentYear) updateConferenceUI(year, false); renderArchiveList(); showToast(year + ' conference restored as active!'); } function deleteYearData(year) { if (!confirm('Permanently DELETE all data for ' + year + '? This CANNOT be undone.')) return; localStorage.removeItem(getYearKey(year)); conferenceData[year] = null; renderArchiveList(); if (year === currentYear) switchConference(year); showToast(year + ' data deleted.'); } // ===================================================== // AUTO-SAVE EVERY 30 SECONDS + ON PAGE EXIT // ===================================================== function collectFormFields() { var fields = {}; // Insurance ['ins-provider','ins-policy','ins-amount','ins-effective','ins-expiry', 'ins-agent','ins-phone','ins-email'].forEach(function(id){ var el = document.getElementById(id); if (el && el.value) fields[id] = el.value; }); // Chapter events (dynamic count - includes any user-added events beyond the original 3) var ceCount = (typeof state.chapterEventCount === 'number') ? state.chapterEventCount : 3; for (var n = 1; n <= ceCount; n++) { ['name','chapter','why','venue','date','time','attendance','audience','dress', 'coord','phone','email','what','price','tickets','entertainment','logistics','transport', 'ins-provider','ins-policy','ins-amount','liquor','security','emergency','permits', 'budget','revenue','sponsors'].forEach(function(f){ var el = document.getElementById('ce'+n+'-'+f); if (el && el.value) fields['ce'+n+'-'+f] = el.value; }); } return fields; } function collectAllNotes() { var notes = {}; document.querySelectorAll('textarea[id^="notes-"]').forEach(function(el){ if (el.value && el.value.trim()) notes[el.id.replace('notes-','')] = el.value; }); return notes; } function saveCurrentYear() { try { // Capture current notes from DOM var currentNotes = collectAllNotes(); Object.assign(state.notes, currentNotes); var payload = { year: currentYear, savedAt: new Date().toISOString(), version: 2, state: JSON.parse(JSON.stringify(state)), formFields: collectFormFields(), notes: currentNotes, archived: (conferenceData[currentYear] && conferenceData[currentYear].archived) || false }; var key = getYearKey(currentYear); localStorage.setItem(key, JSON.stringify(payload)); conferenceData[currentYear] = payload; var el = document.getElementById('conf-last-saved'); if (el) el.textContent = 'Saved ' + new Date().toLocaleTimeString(); try { updateSaveIndicator(true); } catch(e) {} } catch(e) { console.warn('saveCurrentYear error:', e); } } function loadYear(year) { try { var key = getYearKey(year); var saved = localStorage.getItem(key); if (!saved) return false; var data = JSON.parse(saved); if (!data || !data.state) return false; var s = data.state; // Restore simple objects if (s.checkboxes) Object.assign(state.checkboxes, s.checkboxes); if (s.remarksList) Object.assign(state.remarksList, s.remarksList); if (s.dueDates) Object.assign(state.dueDates, s.dueDates); if (s.deletedTasks) Object.assign(state.deletedTasks, s.deletedTasks); if (s.assigned) Object.assign(state.assigned, s.assigned); if (s.addedTasks) Object.assign(state.addedTasks, s.addedTasks); if (s.notes) Object.assign(state.notes, s.notes); if (s.attachments) Object.assign(state.attachments, s.attachments); // Restore arrays if (s.vendors && s.vendors.length) state.vendors = s.vendors; if (s.volunteers && s.volunteers.length) state.volunteers = s.volunteers; if (s.dignitaries && s.dignitaries.length) state.dignitaries = s.dignitaries; if (s.pocs && s.pocs.length) state.pocs = s.pocs; if (s.planningPocs && s.planningPocs.length) state.planningPocs = s.planningPocs; if (s.greetings && s.greetings.length) state.greetings = s.greetings; if (s.budgetIncome && s.budgetIncome.length) state.budgetIncome = s.budgetIncome; if (s.budgetExpense && s.budgetExpense.length) state.budgetExpense = s.budgetExpense; if (s.sponsors && s.sponsors.length) state.sponsors = s.sponsors; if (s.exhibitors && s.exhibitors.length) state.exhibitors = s.exhibitors; if (s.meetings && s.meetings.length) state.meetings = s.meetings; if (s.contracts && s.contracts.length) state.contracts = s.contracts; if (s.resources && s.resources.length) state.resources = s.resources; if (s.agendas && s.agendas.length) state.agendas = s.agendas; if (s.hotels && s.hotels.length) state.hotels = s.hotels; if (s.tourStops && s.tourStops.some(function(t){return t.notes&&t.notes.trim();})) { state.tourStops = s.tourStops; } if (s.customTabs && s.customTabs.length) state.customTabs = s.customTabs; return data.archived || false; } catch(e) { console.warn('loadYear error:', e); return false; } } function restoreFormFields(formFields) { if (!formFields) return; Object.keys(formFields).forEach(function(id){ var el = document.getElementById(id); if (el && formFields[id] !== undefined) el.value = formFields[id]; }); } function restoreNotes(notes) { if (!notes) return; Object.keys(notes).forEach(function(key){ var el = document.getElementById('notes-' + key); if (el && notes[key]) el.value = notes[key]; }); } function rerenderAll() { // Re-render all checklist tabs Object.keys(TABS_DATA).forEach(function(tid){ try { if(TABS_DATA[tid]&&TABS_DATA[tid].sections) renderSections(tid,TABS_DATA[tid].sections); } catch(e) { console.warn('renderSections error for '+tid, e); } }); try { renderInsuranceChecklist(); } catch(e) {} try { renderVendors(); } catch(e) {} try { renderVolunteers(); } catch(e) {} try { renderDignitaries(); } catch(e) {} try { renderPocs(); } catch(e) {} try { renderPlanningPocs(); } catch(e) {} try { renderGreetings(); } catch(e) {} try { renderBudget(); } catch(e) {} try { renderSponsors(); } catch(e) {} try { renderExhibitors(); } catch(e) {} try { renderMeetings(); } catch(e) {} try { renderCalendar(); } catch(e) {} try { renderContracts(); } catch(e) {} try { renderResources(); } catch(e) {} try { renderAgendas(); } catch(e) {} try { renderHotels(); } catch(e) {} try { renderTour(); } catch(e) {} // Restore notes to textareas try { restoreNotes(state.notes); } catch(e) {} // Custom tabs if (state.customTabs && state.customTabs.length) { state.customTabs.forEach(function(t){ try { createCustomTab(t.id, t.name); } catch(e) {} }); try { renderCustomNav(); } catch(e) {} } // Update all progress bars Object.keys(TABS_DATA).forEach(function(tid){ try { updateProgress(tid); } catch(e) {} }); // Attachment lists ['overview','virtual','friday','opening','pageant','stepshow','luncheon','workshop1','workshop2', 'workshop3','orchid','necrology','greetings','vendors','volunteers','dignitaries', 'insurance','tour','poc','planningpoc','chapters','ce1','ce2','ce3','budget', 'sponsors','exhibitors','meetings','contracts','resources','agenda','hotels' ].forEach(function(id){ try { renderAttachments(id); } catch(e) {} }); // Dynamically-added chapter events beyond the original 3 var ceCount = (typeof state.chapterEventCount === 'number') ? state.chapterEventCount : 3; for (var ceN = 4; ceN <= ceCount; ceN++) { try { renderAttachments('ce'+ceN); } catch(e) {} } try { renderAllRemarksLists(); } catch(e) {} try { renderAllCommitteeMembers(); } catch(e) {} // Restore dynamically-added chair tasks first (they need to exist before restoring due dates) try { restoreChairAddedTasks(); } catch(e) {} // Restore due dates + assigned-to into static HTML chair/leadership inputs try { restoreDueDatesAndAssigned(); } catch(e) {} } function initAutoSave() { loadAllYears(); // Try to load saved data for current year var saved = localStorage.getItem(getYearKey(currentYear)); if (saved) { try { var data = JSON.parse(saved); if (data && data.state) { var isArchived = loadYear(currentYear); // Re-render everything with loaded data rerenderAll(); // Restore any dynamically-added chapter event cards before filling in their fields try { restoreChapterEventCards(); } catch(e) {} // Restore any dynamically-added chair tasks from saved state try { restoreChairAddedTasks(); } catch(e) {} // Restore form fields if (data.formFields) restoreFormFields(data.formFields); // Restore due dates and assigned-to into static HTML inputs try { restoreDueDatesAndAssigned(); } catch(e) {} // Update conference UI updateConferenceUI(currentYear, isArchived); // Show load confirmation var savedAt = data.savedAt ? new Date(data.savedAt).toLocaleString() : 'previously'; var el = document.getElementById('conf-last-saved'); if (el) el.textContent = 'Loaded — last saved ' + new Date(data.savedAt).toLocaleTimeString(); try { refreshOverview(); } catch(e) {} } } catch(e) { console.warn('Load on startup error:', e); } } // Auto-save every 10 seconds setInterval(function() { try { saveCurrentYear(); } catch(e) {} }, 10000); // Save on EVERY input change (debounced 800ms) var saveTimer = null; document.addEventListener('input', function() { clearTimeout(saveTimer); var el = document.getElementById('conf-last-saved'); if (el) el.textContent = 'Unsaved changes...'; saveTimer = setTimeout(function() { try { saveCurrentYear(); } catch(e) {} }, 800); }); // Save immediately on change events (date pickers, selects, checkboxes) document.addEventListener('change', function(e) { clearTimeout(saveTimer); // Immediate save for high-value fields: dates, checkboxes, selects var tag = e.target ? e.target.tagName : ''; var type = e.target ? e.target.type : ''; if (type === 'date' || type === 'checkbox' || tag === 'SELECT') { try { saveCurrentYear(); } catch(e2) {} } else { saveTimer = setTimeout(function() { try { saveCurrentYear(); } catch(e2) {} }, 800); } }); // Save immediately on page exit window.addEventListener('beforeunload', function() { try { saveCurrentYear(); } catch(e) {} }); // Also save when tab becomes hidden (mobile / tab switching) document.addEventListener('visibilitychange', function() { if (document.visibilityState === 'hidden') { try { saveCurrentYear(); } catch(e) {} } }); // Freeze event: fires on mobile before tab is killed (more reliable than beforeunload on iOS) document.addEventListener('freeze', function() { try { saveCurrentYear(); } catch(e) {} }); // pagehide: fires on iOS Safari when navigating away (more reliable than beforeunload) window.addEventListener('pagehide', function() { try { saveCurrentYear(); } catch(e) {} }); } // ===================================================== // RESOURCES // ===================================================== function addResourceCard() { if (!state.resources) state.resources = []; state.resources.push({ title:'New Resource', url:'', description:'', files:[] }); renderResources(); } function removeResource(i) { state.resources.splice(i, 1); renderResources(); } function renderResources() { var list = document.getElementById('resources-list'); if (!list) return; if (!state.resources) state.resources = []; if (!state.resources.length) { list.innerHTML = '
    📚

    No resources yet. Click "+ Add Resource" to get started.

    '; return; } list.innerHTML = ''; state.resources.forEach(function(r, i) { var card = document.createElement('div'); card.className = 'resource-card'; card.innerHTML = '
    ' + '📄' + '' + '' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    📎 Files' + '
    ' + '
    ' + renderResFiles(r.files||[],i) + '
    ' + '
    '; list.appendChild(card); }); } function renderResFiles(files, ri) { if (!files.length) return 'No files attached.'; return files.map(function(f,fi){ return '
    📄'+esc(f.name)+''+f.date+' · '+f.size+'' +'
    '; }).join(''); } function attachToResource(input, ri) { if (!state.resources[ri].files) state.resources[ri].files = []; Array.from(input.files).forEach(function(f){ state.resources[ri].files.push({name:f.name, date:new Date().toLocaleDateString(), size:fmtSize(f.size)}); }); input.value = ''; renderResources(); } function removeResFile(ri, fi) { state.resources[ri].files.splice(fi, 1); renderResources(); } // ===================================================== // AGENDAS // ===================================================== function addAgenda() { if (!state.agendas) state.agendas = []; state.agendas.push({ title:'New Agenda', date:'', description:'', items:[] }); renderAgendas(); } function removeAgenda(i) { if (!confirm('Remove this agenda?')) return; state.agendas.splice(i, 1); renderAgendas(); } function addAgendaItem(i) { state.agendas[i].items.push({ time:'', item:'', speaker:'' }); renderAgendas(); } function removeAgendaItem(ai, ii) { state.agendas[ai].items.splice(ii, 1); renderAgendas(); } function renderAgendas() { var list = document.getElementById('agenda-list'); if (!list) return; if (!state.agendas) state.agendas = []; if (!state.agendas.length) { list.innerHTML = '
    📋

    No agendas yet. Click "+ Add Agenda" to create one.

    '; return; } list.innerHTML = ''; state.agendas.forEach(function(ag, i) { var card = document.createElement('div'); card.className = 'agenda-card'; var itemsHtml = ''; (ag.items||[]).forEach(function(item, ii) { itemsHtml += '
    ' + '' + '' + '' + '' + '
    '; }); card.innerHTML = '
    ' + '

    📋 ' + esc(ag.title||'Agenda') + '

    ' + '
    ' + '' + '' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + 'Time' + 'Item' + 'Speaker / Lead' + '
    ' + itemsHtml + '
    ' + (ag.items&&ag.items.length===0?'

    No items yet. Click "+ Add Item" to add agenda items.

    ':'') + '' + '
    '; list.appendChild(card); }); } // ===================================================== // HOTELS // ===================================================== function addHotel(type) { if (!state.hotels) state.hotels = []; state.hotels.push({ type:type||'host', name:'', address:'', phone:'', website:'', contactName:'', contactEmail:'', contactPhone:'', groupRate:'', roomBlock:'', cutoffDate:'', checkIn:'', checkOut:'', amenities:'', shuttleInfo:'', notes:'', confirmed:false }); renderHotels(); } function removeHotel(i) { if (!confirm('Remove this hotel?')) return; state.hotels.splice(i, 1); renderHotels(); } function renderHotels() { var list = document.getElementById('hotels-list'); if (!list) return; if (!state.hotels) state.hotels = []; if (!state.hotels.length) { list.innerHTML = '
    🏨

    No hotels added yet. Click "+ Add Host Hotel" or "+ Add Overflow Hotel".

    '; return; } list.innerHTML = ''; state.hotels.forEach(function(h, i) { var isHost = h.type === 'host'; var card = document.createElement('div'); card.className = 'hotel-card'; card.innerHTML = '
    ' + '

    ' + (isHost ? '🏨 Host Hotel' : '🏩 Overflow Hotel') + (h.name?' — '+esc(h.name):'') + '

    ' + '
    ' + ''+(isHost?'HOST HOTEL':'OVERFLOW HOTEL')+'' + ''+( h.confirmed?'CONFIRMED':'PENDING')+'' + '' + '' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    '; list.appendChild(card); }); try{formatAllCurrencyFields();}catch(e){} } // ===================================================== // DELETE CUSTOM SIDEBAR TABS // ===================================================== function deleteCustomTab(tabId) { if (!confirm('Delete this tab permanently?')) return; // Remove from state state.customTabs = state.customTabs.filter(function(t){ return t.id !== tabId; }); // Remove DOM elements var panel = document.getElementById('tab-' + tabId); if (panel) panel.remove(); var navBtn = document.getElementById('nav-' + tabId); if (navBtn) navBtn.remove(); renderCustomNav(); showToast('Tab deleted.'); showTab('overview'); } // ===================================================== // PRINT CENTER // ===================================================== // ── LEGACY PERMISSIONS TAB REDIRECT ── // The old in-tab user table was superseded by the Admin Panel (apAddUser/apRenderUserList). // These stubs keep any remaining references working by opening the real, functional panel. function addUserRow() { openAdminPanel('users'); } function renderPermTable() { var tbody = document.getElementById('perm-tbody'); if (!tbody) return; loadAuthDB(); tbody.innerHTML = '' + 'User management has moved. ' + ''; } // ===================================================== // ADD CATEGORY / SUB-CATEGORY / TASK — v4 (UNIFIED) // ===================================================== var _pbsDeleteCb = null; var PBS_BUILTIN_CATS = ['Overview', 'State Leadership', 'Conference Executive Staff', 'Registration', 'Programs', 'Finance & Partnerships', 'Support', 'Local Support', 'Governance', 'Communications', 'Awards', 'Conference Resources', 'Logistics', 'Administrative Actions']; // ── MODAL ── function openAddTabModal() { pbsRefreshCatDropdown(); var modal = document.getElementById('add-section-modal'); if (!modal) return; modal.style.display = 'flex'; var el = document.getElementById('add-new-cat-name'); if (el) el.value = ''; var sub = document.getElementById('add-sub-name'); if (sub) { sub.value = ''; setTimeout(function(){ sub.focus(); }, 200); } var icon = document.getElementById('add-section-icon'); if (icon) icon.value = ''; pbsOnCatSelectChange(); } function closeAddSectionModal() { var m = document.getElementById('add-section-modal'); if (m) m.style.display = 'none'; } // Called when the category dropdown changes function pbsOnCatSelectChange() { var sel = document.getElementById('add-cat-select'); var newRow = document.getElementById('new-cat-row'); var newInp = document.getElementById('add-new-cat-name'); if (!sel || !newRow) return; if (sel.value === '__new__') { newRow.style.display = 'block'; if (newInp) setTimeout(function(){ newInp.focus(); }, 50); } else { newRow.style.display = 'none'; } } // Rebuild the category dropdown from the live sidebar + any custom categories function pbsRefreshCatDropdown() { var sel = document.getElementById('add-cat-select'); if (!sel) return; // Collect all current sidebar labels var navContainer = document.getElementById('sidebar-nav'); var liveLabels = []; if (navContainer) { var labels = navContainer.querySelectorAll('.sidebar-section-label'); labels.forEach(function(lbl) { var t = lbl.textContent.replace(/✕/g,'').trim(); if (t) liveLabels.push(t); }); } // Rebuild options sel.innerHTML = ''; // Existing categories group var grp = document.createElement('optgroup'); grp.label = '── Existing Categories ──'; liveLabels.forEach(function(cat) { var opt = document.createElement('option'); opt.value = cat; opt.textContent = cat; grp.appendChild(opt); }); sel.appendChild(grp); // New category option var newOpt = document.createElement('option'); newOpt.value = '__new__'; newOpt.textContent = '✚ Create New Category...'; newOpt.style.fontWeight = '700'; newOpt.style.color = '#1a52c4'; sel.appendChild(newOpt); } // ── CREATE (category + sub-category together) ── function pbsCreateSection() { var sel = document.getElementById('add-cat-select'); var newCat = (document.getElementById('add-new-cat-name')||{}).value || ''; var subName = ((document.getElementById('add-sub-name')||{}).value||'').trim(); var icon = ((document.getElementById('add-section-icon')||{}).value||'').trim() || '📄'; // Validate if (!subName) { var sub = document.getElementById('add-sub-name'); if (sub) { sub.style.borderColor='#ef4444'; sub.focus(); setTimeout(function(){sub.style.borderColor='#e5e7eb';},2000); } showToast('Please enter a sub-category name.','error'); return; } var categoryName = ''; var creatingNewCat = sel && sel.value === '__new__'; if (creatingNewCat) { categoryName = newCat.trim(); if (!categoryName) { var ni = document.getElementById('add-new-cat-name'); if (ni) { ni.style.borderColor='#ef4444'; ni.focus(); setTimeout(function(){ni.style.borderColor='#1a52c4';},2000); } showToast('Please enter a name for the new category.','error'); return; } pbsBuildCategory(categoryName); } else { categoryName = sel ? sel.value : ''; } // Build the sub-category under that category pbsBuildSubCategory(categoryName, subName, icon); closeAddSectionModal(); showToast('"' + subName + '" added under "' + categoryName + '" -- click it in the sidebar to add tasks.'); } // ── BUILD a new sidebar category label ── function pbsBuildCategory(name) { // Don't duplicate if (document.querySelector('.sidebar-section-label[data-cat="' + name.replace(/"/g,'') + '"]')) return; var navContainer = document.getElementById('sidebar-nav'); if (!navContainer) return; var lbl = document.createElement('div'); lbl.className = 'sidebar-section-label'; lbl.setAttribute('data-cat', name); lbl.style.position = 'relative'; var sp = document.createElement('span'); sp.textContent = name; var dx = document.createElement('span'); dx.textContent = '✕'; dx.title = 'Delete this category'; dx.style.cssText = 'position:absolute;right:8px;top:50%;transform:translateY(-50%);cursor:pointer;font-size:.7rem;opacity:.4;padding:2px 5px;border-radius:3px;transition:opacity .15s'; dx.onmouseenter = function(){ this.style.opacity='1'; }; dx.onmouseleave = function(){ this.style.opacity='.4'; }; dx.onclick = function(e) { e.stopPropagation(); confirmDelete('Delete category "'+name+'"?', 'The category label will be removed from the sidebar. Sub-categories under it will remain.', function(){ lbl.remove(); pbsSaveSidebarState(); }); }; lbl.appendChild(sp); lbl.appendChild(dx); // Insert before the + Add Section button var addBtn = navContainer.querySelector('button.btn-blue'); if (addBtn) navContainer.insertBefore(lbl, addBtn); else navContainer.appendChild(lbl); pbsSaveSidebarState(); } // ── BUILD a new sub-category tab + nav button ── function pbsBuildSubCategory(categoryName, subName, icon) { var tabId = 'custom_' + Date.now(); var fullLabel = icon + ' ' + subName; var navContainer = document.getElementById('sidebar-nav'); if (!navContainer) return; // ── NAV BUTTON ── var navBtn = document.createElement('button'); navBtn.className = 'nav-btn'; navBtn.id = 'nav-' + tabId; navBtn.style.cssText = 'display:flex;align-items:center;width:100%;text-align:left;gap:0'; var navSp = document.createElement('span'); navSp.style.cssText = 'flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap'; navSp.textContent = fullLabel; var navX = document.createElement('span'); navX.textContent = '✕'; navX.title = 'Delete "' + subName + '"'; navX.style.cssText = 'font-size:.65rem;opacity:0;padding:2px 5px;flex-shrink:0;border-radius:3px;transition:opacity .15s;cursor:pointer'; navBtn.onmouseenter = function(){ navX.style.opacity='.6'; }; navBtn.onmouseleave = function(){ navX.style.opacity='0'; }; navBtn.onclick = function(e) { if (e.target === navX) { pbsDeleteCustomSubCat(tabId, subName); return; } showTab(tabId); }; navX.onclick = function(e){ e.stopPropagation(); pbsDeleteCustomSubCat(tabId, subName); }; navBtn.appendChild(navSp); navBtn.appendChild(navX); // Insert after the last nav-btn under the target category var children = Array.from(navContainer.children); var insertAfter = null; var inCat = false; for (var i = 0; i < children.length; i++) { var el = children[i]; var isLbl = el.classList.contains('sidebar-section-label'); var lblTxt = isLbl ? el.textContent.replace(/✕/g,'').trim() : ''; if (isLbl && lblTxt === categoryName) { inCat = true; insertAfter = el; } else if (inCat && el.classList.contains('nav-btn')) { insertAfter = el; } else if (inCat && isLbl) { break; } } if (insertAfter && insertAfter.nextElementSibling) { navContainer.insertBefore(navBtn, insertAfter.nextElementSibling); } else if (insertAfter) { var addBtn2 = navContainer.querySelector('button.btn-blue'); if (addBtn2) navContainer.insertBefore(navBtn, addBtn2); else navContainer.appendChild(navBtn); } else { var addBtn3 = navContainer.querySelector('button.btn-blue'); if (addBtn3) navContainer.insertBefore(navBtn, addBtn3); else navContainer.appendChild(navBtn); } // ── TAB PANEL ── pbsBuildTabPanel(tabId, fullLabel, categoryName); // Save to state if (!state.customTabs) state.customTabs = []; state.customTabs.push({ id:tabId, name:subName, icon:icon, parent:categoryName, type:'subcategory' }); pbsSaveSidebarState(); // Navigate to the new tab setTimeout(function(){ showTab(tabId); }, 200); } // ── BUILD the tab panel DOM for a sub-category ── function pbsBuildTabPanel(tabId, fullLabel, parentCat) { var mainEl = document.querySelector('main'); if (!mainEl || document.getElementById('tab-'+tabId)) return; var tabDiv = document.createElement('div'); tabDiv.className = 'tab-panel'; tabDiv.id = 'tab-' + tabId; // Header var ph = document.createElement('div'); ph.className = 'panel-header'; var phL = document.createElement('div'); var h2 = document.createElement('h2'); h2.textContent = fullLabel; var hp = document.createElement('p'); hp.textContent = parentCat; phL.appendChild(h2); phL.appendChild(hp); var phR = document.createElement('div'); phR.className = 'panel-actions no-print'; var delBtn = document.createElement('button'); delBtn.className = 'btn btn-sm'; delBtn.style.cssText = 'background:rgba(185,28,28,.1);color:#b91c1c;border:1px solid rgba(185,28,28,.2)'; delBtn.textContent = '🗑 Delete Tab'; (function(tid, nm){ delBtn.onclick = function(){ pbsDeleteCustomSubCat(tid, nm); }; })(tabId, h2.textContent); phR.appendChild(delBtn); ph.appendChild(phL); ph.appendChild(phR); tabDiv.appendChild(ph); // Task section var sc = document.createElement('div'); sc.className = 'section-card'; var scH = document.createElement('div'); scH.className = 'section-header'; scH.style.cursor='default'; var scH3 = document.createElement('h3'); scH3.textContent = '✅ Tasks'; var addBtn = document.createElement('button'); addBtn.className = 'btn btn-blue btn-sm no-print'; addBtn.textContent = '+ Add Task'; (function(tid){ addBtn.onclick = function(){ pbsAddTask(tid); }; })(tabId); scH.appendChild(scH3); scH.appendChild(addBtn); var scB = document.createElement('div'); scB.className = 'section-body'; var ul = document.createElement('ul'); ul.className='check-list'; ul.id='sections-'+tabId; ul.style.cssText='list-style:none;padding:0;margin:0'; // Input row always at bottom var addRow = document.createElement('li'); addRow.className='add-task-row no-print'; addRow.id='add-task-row-'+tabId; addRow.style.cssText='display:flex;align-items:center;gap:8px;padding:10px 4px'; var addInp = document.createElement('input'); addInp.type='text'; addInp.id='new-task-'+tabId; addInp.placeholder='Type a task and press Enter or click Add...'; addInp.style.cssText='flex:1;padding:8px 12px;border:1.5px solid rgba(26,82,196,.25);border-radius:6px;font-size:.88rem;outline:none;color:#0f2a6e'; addInp.onfocus=function(){this.style.borderColor='#1a52c4';}; addInp.onblur=function(){this.style.borderColor='rgba(26,82,196,.25)';}; (function(tid){ addInp.onkeydown=function(e){if(e.key==='Enter')pbsAddTask(tid);}; })(tabId); var addLiBtn = document.createElement('button'); addLiBtn.className='btn btn-blue btn-sm'; addLiBtn.textContent='+ Add'; (function(tid){ addLiBtn.onclick=function(){pbsAddTask(tid);}; })(tabId); addRow.appendChild(addInp); addRow.appendChild(addLiBtn); ul.appendChild(addRow); scB.appendChild(ul); sc.appendChild(scH); sc.appendChild(scB); tabDiv.appendChild(sc); // Notes var ns=document.createElement('div'); ns.className='notes-section'; ns.style.marginTop='14px'; var nsH=document.createElement('div'); nsH.className='notes-header'; nsH.textContent='📝 Notes'; var nsTA=document.createElement('textarea'); nsTA.id='notes-'+tabId; nsTA.placeholder='Notes for this section...'; ns.appendChild(nsH); ns.appendChild(nsTA); tabDiv.appendChild(ns); // Attachments var att=document.createElement('div'); att.className='attachments-section'; att.style.marginTop='12px'; var attH=document.createElement('div'); attH.className='attachments-header'; var attSp=document.createElement('span'); attSp.textContent='📎 Attachments'; var attLbl=document.createElement('label'); attLbl.className='btn btn-outline btn-sm'; attLbl.style.cssText='color:var(--royal);border-color:var(--royal);cursor:pointer'; attLbl.textContent='+ Add File'; var attFile=document.createElement('input'); attFile.type='file'; attFile.multiple=true; attFile.style.display='none'; (function(tid){ attFile.onchange=function(){addAttachment(this,tid);}; })(tabId); attLbl.appendChild(attFile); attH.appendChild(attSp); attH.appendChild(attLbl); var attList=document.createElement('div'); attList.className='attachment-list'; attList.id='attach-'+tabId; att.appendChild(attH); att.appendChild(attList); tabDiv.appendChild(att); mainEl.appendChild(tabDiv); } // ── ADD TASK to a custom tab ── function pbsAddTask(tabId) { var inp = document.getElementById('new-task-' + tabId); var text = inp ? inp.value.trim() : ''; if (!text) { if (inp) { inp.style.borderColor='#ef4444'; inp.focus(); setTimeout(function(){inp.style.borderColor='rgba(26,82,196,.25)';},1800); } showToast('Please type a task first.','error'); return; } var list = document.getElementById('sections-' + tabId); if (!list) { showToast('Task list not found.','error'); return; } var addRow = document.getElementById('add-task-row-' + tabId); var key = tabId + '-t-' + Date.now(); // Save to state so task persists on reload saveChairTaskToState(tabId, key, text); var li = document.createElement('li'); li.className='check-item'; li.id='ci-'+key; // Checkbox + label + delete button var topDiv = document.createElement('div'); topDiv.className='check-item-top'; var cb = document.createElement('input'); cb.type='checkbox'; cb.id='cb-'+key; cb.setAttribute('data-key',key); cb.addEventListener('change',function(){ state.checkboxes[this.getAttribute('data-key')]=this.checked; var k=this.getAttribute('data-key'); var dEl=document.getElementById('due-'+k); if(dEl) pbsRefreshDueColors(dEl,k); try{saveCurrentYear();}catch(e){} }); var lbl=document.createElement('label'); lbl.htmlFor='cb-'+key; lbl.textContent=text; var del=document.createElement('button'); del.className='delete-task-btn no-print'; del.title='Delete task'; del.textContent='🗑'; del.addEventListener('click',function(){ confirmDelete('Delete task?','"'+text+'"',function(){ li.remove(); removeChairTaskFromState(tabId, key); try{saveCurrentYear();}catch(e){} }); }); topDiv.appendChild(cb); topDiv.appendChild(lbl); topDiv.appendChild(del); // Due date + Assigned To var meta=document.createElement('div'); meta.style.cssText='display:flex;align-items:center;gap:14px;padding:4px 0 4px 28px;flex-wrap:wrap'; var dW=document.createElement('div'); dW.style.cssText='display:flex;align-items:center;gap:6px'; var dL=document.createElement('label'); dL.style.cssText="font-family:'Share Tech Mono',monospace;font-size:.63rem;color:var(--mid);text-transform:uppercase;letter-spacing:.06em;white-space:nowrap"; dL.textContent='Due Date'; var dI=document.createElement('input'); dI.type='date'; dI.id='due-'+key; dI.style.cssText='border:1px solid rgba(26,82,196,.2);border-radius:4px;padding:2px 7px;font-size:.78rem;color:var(--royal-dark);background:#f8faff'; dI.setAttribute('data-key',key); dI.addEventListener('change',function(){ var k=this.getAttribute('data-key'); state.dueDates[k]=this.value; pbsRefreshDueColors(this,k); try{saveCurrentYear();}catch(e){} }); dW.appendChild(dL); dW.appendChild(dI); var aW=document.createElement('div'); aW.style.cssText='display:flex;align-items:center;gap:6px'; var aL=document.createElement('label'); aL.style.cssText="font-family:'Share Tech Mono',monospace;font-size:.63rem;color:var(--mid);text-transform:uppercase;letter-spacing:.06em;white-space:nowrap"; aL.textContent='Assigned To'; var aI=document.createElement('input'); aI.type='text'; aI.id='asgn-'+key; aI.placeholder='Name'; aI.style.cssText='border:1px solid rgba(26,82,196,.2);border-radius:4px;padding:2px 8px;font-size:.78rem;color:var(--royal-dark);background:#f8faff;width:120px'; aI.setAttribute('data-key',key); aI.addEventListener('change',function(){ state.assigned[this.getAttribute('data-key')]=this.value; }); aW.appendChild(aL); aW.appendChild(aI); meta.appendChild(dW); meta.appendChild(aW); // Remarks var rh=document.createElement('div'); rh.className='remarks-history'; rh.id='rh-'+key; var rmkRow=document.createElement('div'); rmkRow.className='remarks-add-row'; var rmkLbl=document.createElement('span'); rmkLbl.className='remarks-label'; rmkLbl.textContent='Remarks:'; var rmkInp=document.createElement('input'); rmkInp.type='text'; rmkInp.placeholder='Add remark and press Save...'; rmkInp.setAttribute('data-rkey',key); var rmkBtn=document.createElement('button'); rmkBtn.textContent='Save'; rmkBtn.setAttribute('data-rkey',key); rmkBtn.setAttribute('data-tid',tabId); rmkBtn.onclick=function(){saveRemark(this.getAttribute('data-rkey'),this.getAttribute('data-tid'));}; rmkInp.onkeydown=function(e){if(e.key==='Enter'){var k=this.getAttribute('data-rkey');saveRemark(k,tabId);}}; rmkRow.appendChild(rmkLbl); rmkRow.appendChild(rmkInp); rmkRow.appendChild(rmkBtn); li.appendChild(topDiv); li.appendChild(meta); li.appendChild(rh); li.appendChild(rmkRow); if (addRow) list.insertBefore(li,addRow); else list.appendChild(li); if (inp) inp.value=''; try{saveCurrentYear();}catch(e){} } function addCustomTask(tabId){ pbsAddTask(tabId); } // ── DELETE ── function pbsDeleteCustomSubCat(tabId, name) { confirmDelete('Delete "'+name+'"?','This removes the tab and all its tasks permanently.',function(){ var nav=document.getElementById('nav-'+tabId); if(nav) nav.remove(); var tab=document.getElementById('tab-'+tabId); if(tab) tab.remove(); if(state.customTabs) state.customTabs=state.customTabs.filter(function(t){return t.id!==tabId;}); pbsSaveSidebarState(); showTab('overview'); showToast('"'+name+'" deleted.'); }); } function pbsDeleteSubCategory(tabId,name){ pbsDeleteCustomSubCat(tabId,name); } // ── SAVE SIDEBAR CUSTOM STATE ── function pbsSaveSidebarState() { try { saveCurrentYear(); } catch(e) {} } // ── RESTORE on load ── function restoreCustomTabs() { if (!state.customTabs || !state.customTabs.length) return; // First pass: restore category labels state.customTabs.forEach(function(t) { if (t.type === 'category' && !document.getElementById('catLabel_'+t.id)) { pbsBuildCategory(t.name); } }); // Second pass: restore sub-categories state.customTabs.forEach(function(t) { if (t.type === 'subcategory' && !document.getElementById('tab-'+t.id)) { var icon = t.icon || '📄'; var fullLabel = icon + ' ' + t.name; var navContainer = document.getElementById('sidebar-nav'); if (navContainer && !document.getElementById('nav-'+t.id)) { var navBtn=document.createElement('button'); navBtn.className='nav-btn'; navBtn.id='nav-'+t.id; navBtn.style.cssText='display:flex;align-items:center;width:100%;text-align:left;gap:0'; var sp=document.createElement('span'); sp.style.cssText='flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap'; sp.textContent=fullLabel; var dx=document.createElement('span'); dx.textContent='✕'; dx.title='Delete'; dx.style.cssText='font-size:.65rem;opacity:0;padding:2px 5px;flex-shrink:0;cursor:pointer;transition:opacity .15s'; navBtn.onmouseenter=function(){dx.style.opacity='.6';}; navBtn.onmouseleave=function(){dx.style.opacity='0';}; (function(tid,nm){ navBtn.onclick=function(e){if(e.target===dx){pbsDeleteCustomSubCat(tid,nm);return;}showTab(tid);}; dx.onclick=function(e){e.stopPropagation();pbsDeleteCustomSubCat(tid,nm);}; })(t.id,t.name); navBtn.appendChild(sp); navBtn.appendChild(dx); // Insert after parent category var children=Array.from(navContainer.children); var insertAfter=null; var inCat=false; children.forEach(function(el){ var isL=el.classList.contains('sidebar-section-label'); if(isL&&el.textContent.replace(/✕/g,'').trim()===t.parent){inCat=true;insertAfter=el;} else if(inCat&&el.classList.contains('nav-btn')){insertAfter=el;} else if(inCat&&isL){inCat=false;} }); if(insertAfter&&insertAfter.nextElementSibling) navContainer.insertBefore(navBtn,insertAfter.nextElementSibling); else { var ab=navContainer.querySelector('button.btn-blue'); if(ab) navContainer.insertBefore(navBtn,ab); else navContainer.appendChild(navBtn); } } pbsBuildTabPanel(t.id, fullLabel, t.parent||''); } }); } // ── CONFIRM DELETE HELPER ── function confirmDelete(title, msg, cb) { var m=document.getElementById('delete-confirm-modal'); if(!m){ if(confirm(title+'\n'+msg)) cb(); return; } var t=document.getElementById('delete-modal-title'); if(t) t.textContent=title; var p=document.getElementById('delete-modal-msg'); if(p) p.textContent=msg; _pbsDeleteCb=cb; var btn=document.getElementById('delete-confirm-btn'); if(btn) btn.onclick=function(){closeDeleteModal();if(_pbsDeleteCb){_pbsDeleteCb();_pbsDeleteCb=null;}}; m.style.display='flex'; } function closeDeleteModal(){ var m=document.getElementById('delete-confirm-modal'); if(m) m.style.display='none'; } // ===================================================== // PRINT CENTER - COMPLETE REWRITE // ===================================================== var PRINT_TABS_LEADERSHIP = [ {id:'statedir', label:'State Director', group:'leadership'}, {id:'state1vp', label:'State 1st Vice Director', group:'leadership'}, {id:'statecos', label:'State Chief of Staff', group:'leadership'}, ]; var PRINT_TABS_EXECUTIVE = [ {id:'confchair', label:'Conference Chair', group:'executive'}, {id:'financechair', label:'Finance Chair', group:'executive'}, {id:'logisticschair', label:'Logistics & Facilities Chair',group:'executive'}, ]; var PRINT_TABS_OPERATIONS = [ {id:'regchair', label:'Registration & Credentials Chair', group:'operations'}, {id:'techchair', label:'Technology & A/V Chair', group:'operations'}, {id:'securitychair', label:'Security & Risk Mgmt Chair', group:'operations'}, {id:'sarmschair', label:'Sergeant-at-Arms Chair', group:'operations'}, {id:'electionschair', label:'Elections & Nominations Chair', group:'operations'}, {id:'volcoordchair', label:'Volunteer Coordinator Chair', group:'operations'}, ]; var PRINT_TABS_PROGRAMS = [ {id:'progchair', label:'Program & Workshops Chair', group:'programs'}, {id:'ossrchair', label:'OSSR Chair', group:'programs'}, {id:'vipchair', label:'VIP & Protocol Chair', group:'programs'}, {id:'awardschair', label:'Awards & Recognition Chair',group:'programs'}, {id:'socialchair', label:'Social Events Chair', group:'programs'}, {id:'necrochair', label:'Necrology Chair', group:'programs'}, ]; var PRINT_TABS_COMMS = [ {id:'publicitychair', label:'Publicity & Comms Chair', group:'comms'}, {id:'socialmediachair', label:'Social Media & Photography Chair',group:'comms'}, {id:'journalchair', label:'Souvenir Journal Chair', group:'comms'}, {id:'chaprelchair', label:'Chapter Relations Chair', group:'comms'}, ]; var PRINT_TABS_SPONSORSHIP = [ {id:'sponsfundchair', label:'Sponsorship & Fundraising Chair', group:'sponsorship'}, {id:'vendexhibchair', label:'Vendor & Exhibitor Chair', group:'sponsorship'}, ]; var PRINT_TABS_SERVICE = [ {id:'commservchair', label:'Community Service Chair', group:'service'}, {id:'medicalchair', label:'Medical Team Chair', group:'service'}, {id:'hospchair', label:'Hospitality Chair', group:'service'}, ]; var PRINT_TABS_CHECKLIST = [ {id:'virtual', label:'Conference Virtual Session', group:'checklist'}, {id:'friday', label:'Friday Operations Day', group:'checklist'}, {id:'opening', label:'Opening Ceremony', group:'checklist'}, {id:'pageant', label:'Pageant / Step Show', group:'checklist'}, {id:'luncheon', label:'1914 Luncheon', group:'checklist'}, {id:'workshop1', label:'Workshop 1', group:'checklist'}, {id:'workshop2', label:'Workshop 2', group:'checklist'}, {id:'workshop3', label:'Workshop 3', group:'checklist'}, {id:'orchid', label:'Orchid Ball', group:'checklist'}, {id:'necrology', label:'Necrology', group:'checklist'}, {id:'chapters', label:'Local Chapters', group:'checklist'}, {id:'greetings', label:'Greetings & Dignitary Letters', group:'checklist'}, {id:'vendors', label:'Vendors', group:'checklist'}, {id:'volunteers', label:'Volunteers', group:'checklist'}, {id:'dignitaries', label:'Dignitary Management', group:'checklist'}, {id:'insurance', label:'Insurance', group:'checklist'}, {id:'tour', label:'Tour Convention Center', group:'checklist'}, {id:'poc', label:'Convention Center POCs', group:'checklist'}, {id:'planningpoc', label:'Planning Committee POCs', group:'checklist'}, {id:'budget', label:'Budget', group:'checklist'}, {id:'sponsors', label:'Sponsors', group:'checklist'}, {id:'exhibitors', label:'Exhibitors', group:'checklist'}, {id:'meetings', label:'Meetings', group:'checklist'}, {id:'contracts', label:'Contract Repository', group:'checklist'}, {id:'resources', label:'Resources', group:'checklist'}, {id:'agenda', label:'Agenda', group:'checklist'}, {id:'hotels', label:'Hotels', group:'checklist'}, {id:'socialaction',label:'Social Action', group:'checklist'}, ]; var PRINT_TABS_COMMITTEE = [ {id:'confchair', label:'Conference Chair', group:'committee'}, {id:'financechair', label:'Finance Chair', group:'committee'}, {id:'logisticschair', label:'Logistics & Facilities Chair', group:'committee'}, {id:'regchair', label:'Registration & Credentials Chair', group:'committee'}, {id:'techchair', label:'Technology & A/V Chair', group:'committee'}, {id:'securitychair', label:'Security & Risk Mgmt Chair', group:'committee'}, {id:'sarmschair', label:'Sergeant-at-Arms Chair', group:'committee'}, {id:'electionschair', label:'Elections & Nominations Chair', group:'committee'}, {id:'volcoordchair', label:'Volunteer Coordinator Chair', group:'committee'}, {id:'progchair', label:'Program & Workshops Chair', group:'committee'}, {id:'ossrchair', label:'OSSR Chair', group:'committee'}, {id:'vipchair', label:'VIP & Protocol Chair', group:'committee'}, {id:'awardschair', label:'Awards & Recognition Chair', group:'committee'}, {id:'socialchair', label:'Social Events Chair', group:'committee'}, {id:'necrochair', label:'Necrology Chair', group:'committee'}, {id:'publicitychair', label:'Publicity & Comms Chair', group:'committee'}, {id:'socialmediachair',label:'Social Media & Photography Chair', group:'committee'}, {id:'journalchair', label:'Souvenir Journal Chair', group:'committee'}, {id:'chaprelchair', label:'Chapter Relations Chair', group:'committee'}, {id:'sponsfundchair', label:'Sponsorship & Fundraising Chair', group:'committee'}, {id:'vendexhibchair', label:'Vendor & Exhibitor Chair', group:'committee'}, {id:'commservchair', label:'Community Service Chair', group:'committee'}, {id:'medicalchair', label:'Medical Team Chair', group:'committee'}, {id:'hospchair', label:'Hospitality Chair', group:'committee'}, ]; var ALL_PRINT_TABS = PRINT_TABS_LEADERSHIP.concat(PRINT_TABS_EXECUTIVE).concat(PRINT_TABS_OPERATIONS).concat(PRINT_TABS_PROGRAMS).concat(PRINT_TABS_COMMS).concat(PRINT_TABS_SPONSORSHIP).concat(PRINT_TABS_SERVICE).concat(PRINT_TABS_CHECKLIST); var currentPreviewTabId = null; function getTabTaskCount(tabId) { return document.querySelectorAll('#sections-' + tabId + ' input[type="checkbox"]').length; } function renderPrintLists() { var HIERARCHY = [ {cat:'Overview',tabs:[{id:'overview',label:'Master Overview'},{id:'fullplan',label:'Full Combined Plan'}]}, {cat:'State Leadership',tabs:[{id:'statedir',label:'State Director'},{id:'state1vp',label:'State 1st Vice Director'},{id:'statecos',label:'State Chief of Staff'}]}, {cat:'Conference Executive Staff',tabs:[{id:'confchair',label:'Conference / Logistics Chair'},{id:'financechair',label:'State Treasurer'}]}, {cat:'Registration',tabs:[{id:'regchair',label:'Registration Committee'}]}, {cat:'Programs',tabs:[{id:'virtual',label:'Conference Virtual Session'},{id:'opening',label:'Opening Ceremony'},{id:'workshop1',label:'Workshop 1'},{id:'workshop2',label:'Workshop 2'},{id:'workshop3',label:'Workshop 3'},{id:'orchid',label:'Orchid Ball'},{id:'luncheon',label:'1914 Luncheon'},{id:'necrology',label:'Necrology'},{id:'pageant',label:'Pageant'},{id:'stepshow',label:'Step Show'},{id:'socialaction',label:'Social Action'}]}, {cat:'Finance & Partnerships',tabs:[{id:'budget',label:'Budget'},{id:'sponsors',label:'Sponsorship & Fundraising'},{id:'exhibitors',label:'Exhibitors'},{id:'vendors',label:'Vendors'},{id:'meetings',label:'Meetings'}]}, {cat:'Support',tabs:[{id:'volcoordchair',label:'Volunteer Coordinator'},{id:'hospchair',label:'Hospitality'},{id:'dignitaries',label:'Dignitary Management / VIP'},{id:'vipchair',label:'Protocol'},{id:'techchair',label:'Technology & A/V'},{id:'securitychair',label:'Security / Sergeant-At-Arms'}]}, {cat:'Local Support',tabs:[{id:'chapters',label:'Local Chapters'},{id:'medicalchair',label:'Medical Team'}]}, {cat:'Governance',tabs:[{id:'sarmschair',label:'Nominations Committee'},{id:'electionschair',label:'Elections Committee'}]}, {cat:'Communications',tabs:[{id:'socialmediachair',label:'Social Media'},{id:'publicitychair',label:'Publicity & Comms'},{id:'journalchair',label:'Souvenir Journal'},{id:'chaprelchair',label:'Chapter Relations'}]}, {cat:'Awards',tabs:[{id:'ossrchair',label:'OSSR'},{id:'awardschair',label:'Awards & Recognition'}]}, {cat:'Conference Resources',tabs:[{id:'contracts',label:'Contract Repository'},{id:'agenda',label:'Agendas'},{id:'poc',label:'Internal Conference POCs'},{id:'planningpoc',label:'External Conference POCs'},{id:'resources',label:'Resources'}]}, {cat:'Logistics',tabs:[{id:'friday',label:'Friday Operations'},{id:'insurance',label:'Insurance'},{id:'tour',label:'Convention Tour'},{id:'hotels',label:'Hotels'}]}, ]; var master = document.getElementById('print-list-master'); if (!master) return; master.innerHTML = ''; HIERARCHY.forEach(function(category) { var catDiv = document.createElement('div'); catDiv.style.cssText = 'margin-bottom:10px;border:1px solid rgba(26,82,196,.18);border-radius:8px;overflow:hidden'; // Category header var catHdr = document.createElement('div'); catHdr.style.cssText = 'display:flex;align-items:center;gap:8px;padding:7px 12px;background:linear-gradient(90deg,rgba(26,82,196,.1),rgba(26,82,196,.04));border-bottom:1px solid rgba(26,82,196,.1);cursor:pointer'; var catCb = document.createElement('input'); catCb.type='checkbox'; catCb.style.cssText='width:15px;height:15px;accent-color:#1a52c4;flex-shrink:0'; catCb.addEventListener('change',function(){ var checked=this.checked; category.tabs.forEach(function(t){ var c=document.getElementById('pcheck-'+t.id); if(c){c.checked=checked;updateRowHighlight(t.id);} }); updatePrintSummary(); }); var catLbl = document.createElement('span'); catLbl.style.cssText="font-family:'Playfair Display',serif;font-size:.88rem;font-weight:700;color:#0f3a8f;flex:1"; catLbl.textContent = category.cat; var catCnt = document.createElement('span'); catCnt.style.cssText="font-family:'Share Tech Mono',monospace;font-size:.63rem;color:var(--mid);background:rgba(26,82,196,.07);padding:2px 7px;border-radius:8px"; catCnt.textContent = category.tabs.length + ' lists'; catHdr.appendChild(catCb); catHdr.appendChild(catLbl); catHdr.appendChild(catCnt); catDiv.appendChild(catHdr); // Sub-category rows var subList = document.createElement('div'); subList.style.background='#fff'; category.tabs.forEach(function(tab,ti) { var count=getTabTaskCount(tab.id); var row=document.createElement('div'); row.className='print-select-row'; row.id='print-row-'+tab.id; row.style.cssText='display:flex;align-items:center;gap:8px;padding:5px 12px 5px 28px;'+(ti= 0; }), }; var tabs = groupTabMap[group] || []; tabs.forEach(function(tab) { var cb = document.getElementById('pcheck-'+tab.id); if (cb) cb.checked = checked; updateRowHighlight(tab.id); }); updatePrintSummary(); } function selectGroupPrint(group) { var gc = document.getElementById('group-check-' + group); if (gc) gc.checked = true; toggleGroupCheck(group, true); } function selectAllPrint() { ALL_PRINT_TABS.forEach(function(tab) { var cb = document.getElementById('pcheck-'+tab.id); if (cb && !cb.disabled) cb.checked = true; updateRowHighlight(tab.id); }); var gca = document.getElementById('group-check-checklist'); var gcb = document.getElementById('group-check-committee'); if (gca) gca.checked = true; if (gcb) gcb.checked = true; updatePrintSummary(); } function clearAllPrint() { ALL_PRINT_TABS.forEach(function(tab) { var cb = document.getElementById('pcheck-'+tab.id); if (cb) cb.checked = false; updateRowHighlight(tab.id); }); var gca = document.getElementById('group-check-checklist'); var gcb = document.getElementById('group-check-committee'); if (gca) gca.checked = false; if (gcb) gcb.checked = false; updatePrintSummary(); } // PREVIEW function showPreview(tabId, tabLabel) { currentPreviewTabId = tabId; var panel = document.getElementById('print-preview-panel'); var titleEl = document.getElementById('preview-title'); var contentEl = document.getElementById('preview-content'); if (!panel || !contentEl) return; titleEl.textContent = '👁 Preview: ' + tabLabel; contentEl.innerHTML = buildPreviewHTML(tabId, tabLabel); panel.style.display = 'block'; panel.scrollIntoView({behavior:'smooth', block:'start'}); } function closePreview() { var panel = document.getElementById('print-preview-panel'); if (panel) panel.style.display = 'none'; currentPreviewTabId = null; } function buildPreviewHTML(tabId, tabLabel) { var checks = document.querySelectorAll('#sections-'+tabId+' .check-item'); if (!checks.length) return '

    No tasks in this list yet.

    '; var nameEl = document.getElementById(tabId+'-name'); var chairName = nameEl && nameEl.value ? nameEl.value : ''; var phoneEl = document.getElementById(tabId+'-phone'); var emailEl = document.getElementById(tabId+'-email'); var html = '
    ' + '' + tabLabel + ''; if (chairName) html += '
    Chair: ' + chairName; if (phoneEl && phoneEl.value) html += '  ·  ' + phoneEl.value; if (emailEl && emailEl.value) html += '  ·  ' + emailEl.value; if (chairName) html += ''; html += '
    '; html += '' + '' + '' + '' + '' + '' + '' + ''; checks.forEach(function(item, i) { var labelEl = item.querySelector('label'); var cbEl = item.querySelector('input[type="checkbox"]'); var text = labelEl ? labelEl.textContent.trim() : ''; var checked = cbEl ? cbEl.checked : false; var key = cbEl ? cbEl.id.replace('cb-','') : ''; var dueEl = document.getElementById('due-'+key); var asgnEl = document.getElementById('asgn-'+key); var due = dueEl ? dueEl.value : (state.dueDates && state.dueDates[key] ? state.dueDates[key] : ''); var asgn = asgnEl ? asgnEl.value : (state.assigned && state.assigned[key] ? state.assigned[key] : ''); var bg = i % 2 === 0 ? '#fff' : '#f8faff'; var statusHtml = checked ? 'Done' : 'Open'; html += '' + '' + '' + '' + '' + '' + ''; }); html += '
    TaskAssigned ToDue DateStatus
    ' + (checked ? '✅' : '☐') + '' + text + '' + asgn + '' + (due ? new Date(due+'T00:00:00').toLocaleDateString() : '') + '' + statusHtml + '
    '; var notesEl = document.getElementById('notes-'+tabId); if (notesEl && notesEl.value && notesEl.value.trim()) { html += '
    ' + 'Notes:' + '

    ' + notesEl.value.trim() + '

    '; } return html; } function printPreviewedTab() { if (!currentPreviewTabId) { showToast('No list is being previewed.', 'error'); return; } var tab = ALL_PRINT_TABS.find(function(t){ return t.id === currentPreviewTabId; }); if (tab) openPrintWindow([tab], tab.label); } // Also add individual print button per chair tab function printChairTab(tabId, tabLabel) { var tab = ALL_PRINT_TABS.find(function(t){ return t.id === tabId; }); if (!tab) tab = {id:tabId, label:tabLabel||tabId, group:'committee'}; openPrintWindow([tab], tabLabel||tabId); } function printSelectedTabs() { var selected = ALL_PRINT_TABS.filter(function(t) { var cb = document.getElementById('pcheck-'+t.id); return cb && cb.checked && !cb.disabled; }); if (!selected.length) { showToast('Please select at least one list to print.', 'error'); return; } openPrintWindow(selected, 'Selected Task Lists'); } function printAllTabs() { var withTasks = ALL_PRINT_TABS.filter(function(t){ return getTabTaskCount(t.id) > 0; }); if (!withTasks.length) { showToast('No task lists have tasks yet.', 'error'); return; } openPrintWindow(withTasks, 'All Task Lists'); } // ===================================================== // PDF SAVE FUNCTIONS // ===================================================== function saveSelectedAsPDF() { var selected = ALL_PRINT_TABS.filter(function(t) { var cb = document.getElementById('pcheck-'+t.id); return cb && cb.checked; }); if (!selected.length) { showToast('Please select at least one list to save as PDF.', 'error'); return; } openPDFWindow(selected, 'Selected Task Lists'); } function savePDFPreviewedTab() { if (!currentPreviewTabId) { showToast('No list is being previewed.', 'error'); return; } var tab = ALL_PRINT_TABS.find(function(t){ return t.id === currentPreviewTabId; }); if (tab) openPDFWindow([tab], tab.label); } function savePDFAllTabs() { openPDFWindow(ALL_PRINT_TABS, 'All Task Lists'); } function openPDFWindow(tabs, title) { // Build the same HTML as the print window var sections = buildPrintSections(tabs); var totalTasks = tabs.reduce(function(acc,t){ return acc + getTabTaskCount(t.id); }, 0); var htmlContent = '' + 'PBS GA Conference ' + currentYear + ' - ' + title + '' + '' + '
    ' + '💾 Save as PDF Instructions' + 'Press Ctrl+P (Windows) or Cmd+P (Mac) → ' + 'Change Destination to "Save as PDF" → Click Save' + '
    ' + '
    ' + '
    Phi Beta Sigma Fraternity, Inc.
    ' + '
    State of Georgia Conference ' + currentYear + ' — ' + title + '
    ' + '
    ' + new Date().toLocaleString() + '
    ' + tabs.length + ' list(s)  |  ' + totalTasks + ' total tasks
    ' + '
    ' + sections + '
    Phi Beta Sigma Fraternity, Inc. — State of Georgia Conference Management System — ' + currentYear + '
    ' + ''; var win = window.open('', '_blank', 'width=960,height=800'); if (!win) { showToast('Pop-up blocked. Please allow pop-ups for this site.', 'error'); return; } win.document.write(htmlContent); win.document.close(); } // Shared function to build print/PDF section HTML function buildPrintSections(tabs) { return tabs.map(function(tab, i) { var checks = document.querySelectorAll('#sections-'+tab.id+' .check-item'); var nameEl = document.getElementById(tab.id+'-name'); var chairName = nameEl && nameEl.value ? nameEl.value : ''; var phoneEl = document.getElementById(tab.id+'-phone'); var emailEl = document.getElementById(tab.id+'-email'); var rows = Array.from(checks).map(function(item, ri) { var labelEl = item.querySelector('label'); var cbEl = item.querySelector('input[type="checkbox"]'); var text = labelEl ? labelEl.textContent.trim() : ''; var checked = cbEl ? cbEl.checked : false; var key = cbEl ? cbEl.id.replace('cb-','') : ''; var dueEl = document.getElementById('due-'+key); var asgnEl = document.getElementById('asgn-'+key); var due = dueEl ? dueEl.value : (state.dueDates && state.dueDates[key] ? state.dueDates[key] : ''); var asgn = asgnEl ? asgnEl.value : (state.assigned && state.assigned[key] ? state.assigned[key] : ''); var bg = ri % 2 === 0 ? '#ffffff' : '#f8faff'; return '' + ''+(checked?'✓':'☐')+'' + ''+text+'' + ''+asgn+'' + ''+(due?new Date(due+'T00:00:00').toLocaleDateString():'')+'' + ''+(checked?'Done':'Open')+'' + ''; }).join(''); var notesEl = document.getElementById('notes-'+tab.id); var notesHtml = (notesEl && notesEl.value && notesEl.value.trim()) ? '
    Notes:

    '+notesEl.value.trim()+'

    ' : ''; var pageBreak = i > 0 ? 'page-break-before:always;' : ''; return '
    ' + '
    ' + '
    '+tab.label+'
    ' + (chairName ? '
    Chair: '+chairName+(phoneEl&&phoneEl.value?'  |  '+phoneEl.value:'')+(emailEl&&emailEl.value?'  |  '+emailEl.value:'')+'
    ' : '') + '
    '+currentYear+' Conference  ·  '+new Date().toLocaleDateString()+'
    ' + '
    ' + (checks.length > 0 ? '' + '' + ''+rows+'
    TaskAssigned ToDue DateStatus
    ' : '

    No tasks added yet.

    ') + notesHtml + '
    '; }).join(''); } function openPrintWindow(tabs, title) { var sections = buildPrintSections(tabs); var totalTasks = tabs.reduce(function(acc,t){ return acc + getTabTaskCount(t.id); }, 0); var win = window.open('','_blank','width=960,height=720'); if (!win) { showToast('Pop-up blocked. Please allow pop-ups for this site.', 'error'); return; } win.document.write('' + 'PBS GA Conference '+currentYear+' - '+title+'' + '' + '' + '
    ' + '
    Phi Beta Sigma Fraternity, Inc.
    ' + '
    State of Georgia Conference '+currentYear+' — '+title+'
    ' + '
    '+new Date().toLocaleString()+'
    '+tabs.length+' list(s)  |  '+totalTasks+' total tasks
    ' + '
    ' + sections + '
    Phi Beta Sigma Fraternity, Inc. — State of Georgia Conference Management System — '+currentYear+'
    ' + ''); win.document.close(); } function initPrintCenter() { setTimeout(function() { renderPrintLists(); }, 200); } // ===================================================== // COMMITTEE CHAIR MANAGEMENT // ===================================================== var CHAIR_IDS = ['statedir','state1vp','statecos','confchair','financechair','logisticschair','regchair','techchair', 'securitychair','sarmschair','electionschair','volcoordchair','progchair','ossrchair', 'vipchair','awardschair','socialchair','necrochair','publicitychair','socialmediachair', 'journalchair','chaprelchair','sponsfundchair','vendexhibchair','commservchair', 'medicalchair','hospchair']; // Auto-format currency field inline (called onblur) function fmtCurrField(el) { if (!el || !el.value.trim()) return; var raw = parseFloat(el.value.replace(/[^0-9.-]/g,'')); if (!isNaN(raw)) { el.value = '$' + raw.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g,','); } // Auto-calculate remaining budget var chairId = (el.id||'').replace(/-allocated$/,'').replace(/-spent$/,''); var alloc = document.getElementById(chairId+'-allocated'); var spent = document.getElementById(chairId+'-spent'); var remaining = document.getElementById(chairId+'-remaining'); if (alloc && spent && remaining) { var allocVal = parseFloat((alloc.value||'0').replace(/[^0-9.-]/g,''))||0; var spentVal = parseFloat((spent.value||'0').replace(/[^0-9.-]/g,''))||0; var rem = allocVal - spentVal; remaining.value = '$' + rem.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g,','); remaining.style.color = rem < 0 ? '#c0392b' : '#1e7a4a'; } } // Update visual progress bar for a chair tab function updateChairProgress(chairId) { var checkboxes = document.querySelectorAll('#sections-'+chairId+' input[type="checkbox"]'); var checked = document.querySelectorAll('#sections-'+chairId+' input[type="checkbox"]:checked'); var total = checkboxes.length; var done = checked.length; var pct = total ? Math.round(done/total*100) : 0; var bar = document.getElementById('prog-'+chairId); var label = document.getElementById('prog-label-'+chairId); var count = document.getElementById('prog-count-'+chairId); if (bar) bar.style.width = pct+'%'; if (label) label.textContent = pct+'%'; if (count) count.textContent = done+' / '+total; } // Wire up chair checkboxes after page loads function initChairCheckboxes() { CHAIR_IDS.forEach(function(chairId) { var checks = document.querySelectorAll('#sections-'+chairId+' input[type="checkbox"]'); checks.forEach(function(cb) { cb.addEventListener('change', function() { updateChairProgress(chairId); try { refreshOverview(); } catch(e) {} try { saveCurrentYear(); } catch(e) {} }); }); updateChairProgress(chairId); }); } // Add a custom task to a chair checklist function addChairTask(chairId) { var input = document.getElementById('new-task-' + chairId); var text = input ? input.value.trim() : ''; if (!text) return; var key = chairId + '-custom-' + Date.now(); // Save to state FIRST so it persists across reloads saveChairTaskToState(chairId, key, text); // Build and insert the DOM element buildChairTaskLi(chairId, key, text, false); if (input) input.value = ''; updateChairProgress(chairId); try { saveCurrentYear(); } catch(e) {} } // Collect all chair form field values for saving function collectChairFields() { var fields = {}; var ALL_CHAIR_LIKE_IDS = CHAIR_IDS.concat(['socialaction']); ALL_CHAIR_LIKE_IDS.forEach(function(chairId) { ['name','chapter','phone','email','assigned','status','allocated','spent','remaining', 'cochair-name','cochair-chapter','cochair-phone','cochair-email'].forEach(function(field){ var el = document.getElementById(chairId+'-'+field); if (el && el.value) fields[chairId+'-'+field] = el.value; }); }); // Program tabs use a different field-naming pattern: tabId-event-chair / tabId-event-cochair var PROGRAM_CHAIR_TABS = ['virtual','opening','workshop1','workshop2','workshop3','orchid','necrology','pageant','stepshow','luncheon']; PROGRAM_CHAIR_TABS.forEach(function(tabId) { ['event-chair','event-chair-chapter','event-chair-phone','event-chair-email', 'event-cochair','event-cochair-chapter','event-cochair-phone','event-cochair-email'].forEach(function(field){ var el = document.getElementById(tabId+'-'+field); if (el && el.value) fields[tabId+'-'+field] = el.value; }); }); return fields; } // Restore chair form field values from saved data function restoreChairFields(fields) { if (!fields) return; Object.keys(fields).forEach(function(id) { var el = document.getElementById(id); if (el && fields[id]) { el.value = fields[id]; // Trigger remaining calculation for budget fields if (id.endsWith('-spent') || id.endsWith('-allocated')) { try { fmtCurrField(el); } catch(e) {} } } }); } // Include chair fields in the main save function collectFormFields() { var fields = {}; // Insurance ['ins-provider','ins-policy','ins-amount','ins-effective','ins-expiry', 'ins-agent','ins-phone','ins-email'].forEach(function(id){ var el = document.getElementById(id); if (el && el.value) fields[id] = el.value; }); // Chapter events (dynamic count - includes any user-added events beyond the original 3) var ceCount = (typeof state.chapterEventCount === 'number') ? state.chapterEventCount : 3; for (var n = 1; n <= ceCount; n++) { ['name','chapter','why','venue','date','time','attendance','audience','dress', 'coord','phone','email','what','price','tickets','entertainment','logistics','transport', 'ins-provider','ins-policy','ins-amount','liquor','security','emergency','permits', 'budget','revenue','sponsors'].forEach(function(f){ var el = document.getElementById('ce'+n+'-'+f); if (el && el.value) fields['ce'+n+'-'+f] = el.value; }); } // Committee chair fields var chairFields = collectChairFields(); Object.assign(fields, chairFields); return fields; } // ═══════════ CHECKLIST DATA (no numbers in section titles) ═══════════ const TABS_DATA = { virtual:{sections:[ {t:"Role Assignments (Master Control)",items:["Presiding Officer: Lead session","Presiding Officer: Recognize speakers","Presiding Officer: Maintain order","Parliamentarian: Ensure procedural compliance","Parliamentarian: Advise on motions","Secretary: Record minutes","Secretary: Track motions and votes","Timekeeper: Monitor speaking time","Timekeeper: Enforce time limits","IT/Platform Lead: Manage platform","IT/Platform Lead: Troubleshoot issues","Credentialing Lead: Verify delegates","Credentialing Lead: Maintain quorum tracking"]}, {t:"Registration & Credentialing",items:["Build registration system","Collect: Name","Collect: Chapter","Collect: Voting status","Assign delegate identifiers","Pre-verify credentials","Develop roll call report","Assign monitoring team"]}, {t:"Communication & Access",items:["Generate secure meeting link","Distribute to verified delegates","Send instructions: Login process","Send instructions: Tech requirements","Send instructions: Backup access","Send reminders: 1 week prior","Send reminders: 48 hours prior","Send reminders: Day-of","Provide help desk contact"]}, {t:"Standardized Reporting (State of Georgia Slide)",items:["Create standard slide template: Title","Create standard slide template: Presenter name/title","Create standard slide template: Key accomplishments","Create standard slide template: Metrics/data","Create standard slide template: Challenges","Create standard slide template: Recommendations","Require all presenters to use template","Collect slides","Merge into master deck","Review for consistency"]}, {t:"Program Flow & Business Session Structure",items:["Call to Order/Greetings","Invocation","Delegates Roll Call","Adoption of 2027 Standing Rules","Approval of Agenda","Consent Agenda: 2026 Minutes","Consent Agenda: Executive Summary Reports","Committee Reports","Assign speakers","Define speaking order","Set time limits","Plan contingencies"]}, {t:"Voting Management",items:["Select voting method","Test system","Define eligibility & thresholds","Assign vote monitors","Establish motion process","Create backup voting method"]}, {t:"Audio / Visual Management",items:["Confirm speaker audio readiness","Test sound and sharing","Establish mute protocol","Assign A/V moderator","Setup backup communication"]}, {t:"Testing & Rehearsals",items:["Conduct full rehearsal","Test: Roll call","Test: Voting","Test: Screen sharing","Simulate issues","Conduct leadership walkthrough"]}, {t:"Delegate Management During Session",items:["Monitor attendance","Confirm quorum","Manage speaker queue","Moderate chat","Remove unauthorized participants","Maintain order"]}, {t:"Risk Management & Contingency",items:["Assign backup host","Prepare backup link","Setup backup communication","Plan for: Platform failure","Plan for: Speaker disconnect","Plan for: Voting issues","Define escalation process"]}, {t:"Session Execution Checklist",items:["Platform opens early for login","Credential verification in progress","IT team actively monitoring","Documents accessible","Quorum confirmed","Session proceeds per agenda","Votes recorded accurately","Time managed effectively"]} ]}, friday:{sections:[ {t:"Overall Day Operations Control",items:["Establish command center location","Distribute master run-of-show to all leads","Confirm all team leads are on-site","Communication: Walkie-talkies distributed","Communication: Group chat established","Confirm emergency contacts list","Review full-day flow with leadership team","Assign floaters for problem-solving"]}, {t:"Vendor Setup Management",items:["Confirm vendor list (all vendors accounted for)","Assign vendor load-in schedule","Vendor instructions: Entry points","Vendor instructions: Setup guidelines","Vendor instructions: Contact person","Check-in vendors upon arrival","Direct vendors to assigned spaces","Verify setup: Tables","Verify setup: Chairs","Verify setup: Power access","Ensure vendor signage displayed","Confirm vendor compliance","Walkthrough of all vendor areas","Resolve setup issues","Confirm vendors fully operational"]}, {t:"Registration Operations",items:["Registration tables arranged","Alphabetical check-in lists printed","Name badges organized","Wristbands/credentials prepared","Signage clearly displayed","Assign: Check-in staff","Assign: Problem resolution staff","Assign: Line management","Verify attendee identity","Distribute credentials","Direct attendees to locations","Manage long lines efficiently","Extra badges stocked","Pens and markers stocked","Registration lists backup copies available"]}, {t:"Vendors / Exhibitors Operations",items:["Confirm all booths are staffed","Ensure vendor branding is visible","Monitor vendor engagement","Address vendor issues quickly","Maintain clean and organized vendor area","Ensure compliance with event rules"]}, {t:"1914 Luncheon Management (Day-Of)",items:["Confirm catering arrival","Finalize seating layout","Assign table numbers","Setup head table: Podium","Setup head table: Name placards","Setup head table: Water service","Centerpieces placed","Linens arranged","Stage décor complete","Confirm speakers","Confirm emcee","Prepare scripts","Confirm presentation order","Confirm serving style","Coordinate meal timing with program","Monitor service flow","Seating assistance available","VIP handling in place","Ensure smooth transitions"]}, {t:"Business Session II",items:["Seating arranged (delegates sectioned if needed)","Podium positioned","Microphones tested","A/V system ready","Agenda available","Reports accessible","Voting materials ready","Confirm quorum","Manage speaker queue","Track motions and votes","Maintain order and decorum","Parliamentarian present","Secretary recording minutes","IT/A-V support available"]}, {t:"Pageant / Step Show Management (Day-Of)",items:["Stage setup completed","Lighting configured","Sound system tested","Judges' table arranged","Score sheets prepared","Confirm all contestants/teams checked in","Provide lineup order","Assign staging coordinator","Confirm emcee script","Confirm performance order","Manage transitions between acts","Judges confirmed and briefed","Scoring criteria explained","Score collection process established","Seating organized","Crowd control in place","Energy maintained (music, pacing)"]}, {t:"Audio / Visual Management (Full Day)",items:["Sound systems tested for all spaces","Microphones assigned per area","Backup microphones available","Music coordinated for transitions","A/V technicians assigned per event segment"]}, {t:"Staffing & Volunteers",items:["Assign volunteers: Registration","Assign volunteers: Vendor support","Assign volunteers: Luncheon","Assign volunteers: Business session","Assign volunteers: Pageant/Step show","Provide briefing to all staff","Ensure dress code compliance","Provide meals/breaks for staff"]}, {t:"Risk Management & Contingency",items:["Emergency plan in place","Security assigned","Crowd flow monitored","Vendor compliance enforced","Backup plans: A/V failure","Backup plans: Vendor issues","Backup plans: Schedule delays"]}, {t:"Day-Of Execution Control",items:["Vendors fully set up and operational","Registration running smoothly","Luncheon executed without delay","Business session orderly and controlled","Pageant/Step show transitions seamless","Leadership updated regularly on status"]} ]}, opening:{sections:[ {t:"Command & Control",items:["Establish command center (stage side or control booth)","Assign Stage Manager","Assign A/V Director","Assign Protocol Lead","Assign Floor Manager","Distribute master run-of-show and scripts","Distribute communication devices (radios/headsets)","Establish cue language ('Standby', 'Go')","Maintain real-time communication across teams"]}, {t:"Seating Plan & Control",items:["Seating: GA State Directors","Seating: Regional Directors","Seating: National Leadership","Seating: NPHC Leadership","Seating: Local Elected Officials","Seating: Local Chapter Leadership","Seating: General Audience","Determine stage vs VIP floor seating","Confirm speaker seating flow","Honoree seating near stage"]}, {t:"Seating Layout & Flow",items:["Create seating chart (VIP + General)","Ensure clear center aisle","Ensure unobstructed stage view","Place reserved signage","Position ushers at entry doors","Position ushers in aisles","Fill seating efficiently"]}, {t:"Staging & Holding Areas (Critical)",items:["Establish holding area: Processional participants","Establish holding area: Speakers","Establish holding area: Choir/Performers","Establish holding area: Dignitaries (VIP)","Assign staging coordinator","Ensure next participants are ALWAYS ready early","Maintain quiet, controlled backstage environment"]}, {t:"Protocol & Dignitary Management",items:["Confirm dignitary list (names, titles, order)","Verify correct pronunciation","Assign seating by rank","Assign escorts for arrival","Assign escorts for stage movement","Stage dignitaries in VIP holding area before recognition","Confirm recognition method: Stand or Stage introduction","Manage late arrivals without disruption"]}, {t:"Processional Execution",items:["Line up participants in exact order","Confirm music ready","Cue A/V for processional start","Floor Manager clears aisle","Execute entry","Ushers guide participants to assigned seating","Confirm all seated before next segment"]}, {t:"Flow & Cue Control System",items:["Each segment has assigned owner","Each segment has start cue","Each segment has end cue","Stage Manager cues next segment BEFORE current ends","A/V follows cue sheet strictly","Maintain continuous flow (no silence)"]}, {t:"Music & Transition Control (No Dead Air)",items:["Preload processional music","Preload transition music","Preload walk-up cues","Use music during stage transitions","Use music during movement delays","Fade music in/out smoothly","Never allow silence between segments"]}, {t:"Master & Mistress of Ceremony",items:["Provide final script","Review pronunciation","Conduct rehearsal","Position stage-side before entry","Cue entry","Ensure microphones are live"]}, {t:"Color Guard / Anthem / Pledge",items:["Stage Color Guard","Clear entry path","Cue anthem","Direct audience to stand","Lead pledge","Coordinate exit"]}, {t:"Choir & Performance",items:["Stage choir near entry","Set microphones","Conduct sound check","Cue performance","Manage entry/exit","Assign seating after performance"]}, {t:"Speaker Flow",items:["Seat speakers in order","Position next speaker near stage","Assign mic runners","Manage transitions: Seat to Stage","Manage transitions: Stage to Seat","Timekeeper enforces limits"]}, {t:"Presentations & Honors",items:["Load all slides into one master deck","Stage awards in order","Align presenter and recipient","Cue both simultaneously","Ensure honorees seated near stage"]}, {t:"Sponsor Recognition",items:["Verify sponsor list","Load logos into slides","Align script with visuals","Execute recognition"]}, {t:"Announcements",items:["Prepare script: Reception","Prepare script: Business session","Prepare script: ZHOPE drop-off","Verify details","Assign speaker","Deliver concisely"]}, {t:"Closing & Official Opening",items:["Stage GA State Directors","Prepare microphones","Cue group","Execute official opening declaration"]}, {t:"Audio/Visual Control",items:["Test all microphones","Prepare backups","Load slides","Prepare lighting cues","Monitor real-time execution"]}, {t:"Backstage & Floor Control",items:["Restrict backstage access","Define movement paths","Position ushers","Monitor audience","Maintain order"]}, {t:"Contingency Plan",items:["Backup speakers identified","Printed scripts available","Backup A/V ready","Plan for: Late arrivals","Plan for: Missing participants","Plan for: Equipment failure"]}, {t:"Rehearsal Checklist",items:["Distribute run-of-show and scripts","Confirm attendance of all participants","Review full program verbally","Confirm order and roles","Test microphones","Test music cues","Test slides and lighting","Practice processional","Practice stage movement","Practice transitions","Run emcee script aloud","Confirm timing and pronunciation","Choir performs","Adjust sound levels","Execute full program","Time each segment","Adjust as needed","Simulate issues","Practice backup plans","All roles confirmed","All cues understood","Ceremony Director approves readiness"]} ]}, pageant:{sections:[ {t:"Pre-Event Setup",items:["Stage setup completed","Lighting configured","Sound system tested","Judges' table arranged","Score sheets prepared"]}, {t:"Participants",items:["Confirm all pageant contestants checked in","Provide lineup order","Assign staging coordinator"]}, {t:"Program Flow",items:["Confirm emcee script","Confirm performance order","Manage transitions between acts"]}, {t:"Judging",items:["Judges confirmed and briefed","Scoring criteria explained","Score collection process established"]}, {t:"Audience Experience",items:["Seating organized","Crowd control in place","Energy maintained (music, pacing)"]} ]}, stepshow:{sections:[ {t:"Pre-Event Setup",items:["Stage setup completed","Lighting configured","Sound system tested","Judges' table arranged","Score sheets prepared"]}, {t:"Participants",items:["Confirm all step show teams checked in","Provide lineup order","Assign staging coordinator"]}, {t:"Program Flow",items:["Confirm emcee script","Confirm performance order","Manage transitions between acts"]}, {t:"Judging",items:["Judges confirmed and briefed","Scoring criteria explained","Score collection process established"]}, {t:"Audience Experience",items:["Seating organized","Crowd control in place","Energy maintained (music, pacing)"]} ]}, luncheon:{sections:[ {t:"Room Setup & Layout",items:["Confirm room capacity and layout","Arrange tables (rounds of 8–10 guests)","Setup head table or dais","Podium placement","Ensure clear aisles for service and movement","Ensure unobstructed stage views","Place directional signage"]}, {t:"Seating Plan & Control",items:["Seating: State Directors / Key Leadership","Seating: Special Guests / Honorees","Seating: Sponsors","Seating: General attendees","Assign table numbers","Create seating chart","Place reserved signs at VIP tables","Position ushers to assist with seating","Ensure honorees are seated near stage"]}, {t:"Table Setup Details",items:["Table numbers displayed clearly","Centerpieces placed","Linens and napkins set","Place settings arranged","Water service pre-set","Programs placed at each seat"]}, {t:"Catering & Food Service Logistics",items:["Confirm final guest count","Finalize menu: Salad","Finalize menu: Entrée","Finalize menu: Dessert","Confirm dietary accommodations","Confirm serving style: Plated or Buffet","Align meal service with program flow","Head table served first","Efficient table service rotation","Monitor food quality and timing"]}, {t:"Program Flow & Execution",items:["Develop run-of-show: Opening remarks","Develop run-of-show: Invocation","Develop run-of-show: Meal service","Develop run-of-show: Speakers","Develop run-of-show: Recognition/awards","Assign speakers and order","Set time limits for each segment","Coordinate transitions between food and program"]}, {t:"Speaker & Emcee Management",items:["Confirm emcee","Provide scripts","Confirm speakers: Names and titles","Confirm speakers: Speaking order","Position speakers near stage","Assign mic runner","Timekeeper enforces limits"]}, {t:"Protocol & Dignitary Management",items:["Confirm dignitary list","Verify titles and pronunciation","Assign seating based on rank","Assign escorts for arrival","Assign escorts for stage movement","Stage dignitaries prior to recognition","Manage late arrivals discreetly"]}, {t:"Awards / Recognition Logistics",items:["Confirm award recipients","Prepare awards (labeled and organized)","Assign presenters","Stage awards in order","Coordinate recipient movement","Coordinate presenter timing","Ensure smooth transitions"]}, {t:"Audio / Visual Control",items:["Setup podium microphone","Setup wireless microphones","Prepare backups","Load presentations","Test sound levels","Coordinate entry music","Coordinate transition music","Monitor A/V throughout event"]}, {t:"Music & Ambience",items:["Select background music for guest arrival","Select background music for dining","Prepare transition music between segments","Maintain consistent volume","Avoid silence during transitions"]}, {t:"Flow & Movement Control",items:["Coordinate guest seating flow","Maintain clear aisles","Manage server movement","Manage guest movement","Control entry/exit during program","Ensure minimal disruption during speeches"]}, {t:"Staffing & Volunteers",items:["Assign volunteers: Registration/entry","Assign volunteers: Seating ushers","Assign volunteers: Stage assistants","Provide briefing","Ensure dress code compliance","Provide meals/breaks if needed"]}, {t:"Risk Management & Contingency",items:["Backup speakers identified","Backup A/V equipment ready","Extra seating available","Plan for: Catering delays","Plan for: Speaker absence","Plan for: Overcapacity","Assign escalation contact"]}, {t:"Day-Of Execution Check",items:["Room fully set","Tables prepared","A/V tested","Catering ready","Speakers present","Guests seated smoothly","Program runs on schedule","Transitions smooth","Audience engaged"]} ]}, orchid:{sections:[ {t:"Planning Timeline – Milestones",items:["Milestone 1: Define event purpose","Milestone 1: Confirm event name: Orchid Ball","Milestone 1: Establish master budget","Milestone 1: Identify planning committee & assign roles","Milestone 1: Begin sponsorship outreach","Milestone 1: Secure venue contract","Milestone 1: Confirm ballroom capacity (min 550 buffer)","Milestone 2: Secure caterer (plated service)","Milestone 2: Book live artist","Milestone 2: Secure A/V vendor","Milestone 2: Draft preliminary floor plan","Milestone 2: Launch sponsorship packages","Milestone 2: Confirm stage size, layout, green room","Milestone 3: Launch ticket sales","Milestone 3: Confirm award categories","Milestone 3: Begin recipient selection","Milestone 3: Finalize décor concept (orchid theme)","Milestone 3: Draft run-of-show (v1)","Milestone 3: Confirm ADA accessibility","Milestone 4: Confirm emcee, speakers, presenters","Milestone 4: Finalize menu selections","Milestone 4: Order awards (engraving submitted)","Milestone 4: Finalize sponsorship commitments","Milestone 4: Begin program booklet design","Milestone 4: Confirm insurance requirements","Milestone 5: Finalize seating strategy","Milestone 5: Assign preliminary table numbers","Milestone 5: Confirm décor orders","Milestone 5: Finalize A/V needs and scripts","Milestone 5: Submit program to print","Milestone 5: Confirm security staffing","Milestone 6: Confirm final guest count with caterer","Milestone 6: Print all materials","Milestone 6: Final walkthrough with venue","Milestone 6: Confirm final run-of-show","Milestone 6: Stage awards in order","Milestone 6: Prepare event supply kits","Milestone 7: Execute full setup timeline","Milestone 7: Conduct A/V soundcheck & rehearsal","Milestone 7: Staff/volunteer briefing","Milestone 7: Registration setup complete","Milestone 7: Execute event per run-of-show"]}, {t:"Budget & Cost Tracking",items:["Venue: Rental fee","Venue: Setup/breakdown","Venue: Tables/chairs","Venue: Dance floor","Catering: Cost per plate × 500","Catering: Staff/service fees","Catering: Beverage service","Catering: Dessert upgrades","Catering: Gratuity/service charges","Decor: Centerpieces (orchids)","Decor: Linens & napkins","Decor: Chair covers","Decor: Stage decor","Decor: Lighting (uplighting/spotlights)","Decor: Entry décor","A/V: Sound system","A/V: Microphones","A/V: Screens/projectors","A/V: Lighting tech","A/V: Technicians","Entertainment: Live artist fee","Entertainment: Travel/lodging","Entertainment: DJ/music","Awards: Trophies/plaques","Awards: Engraving","Printing: Programs","Printing: Table numbers","Printing: Seating charts","Printing: Signage","Staffing: Event staff","Staffing: Security","Media: Photographer","Media: Videographer","Administrative: Insurance","Administrative: Ticketing fees","Administrative: Contingency (10–15%)"]}, {t:"Event Foundation & Strategy",items:["Define purpose and goals","Establish budget framework","Create planning structure","Assign Event Chair","Assign Logistics Lead","Assign Sponsorship Lead","Assign Program Director","Assign Finance Lead","Assign Volunteer Coordinator"]}, {t:"Venue & Logistics",items:["Confirm ballroom layout","Stage placement","Dance floor","Green room access","ADA compliance","Insurance compliance","Security plan"]}, {t:"Catering (Plated Service)",items:["Select caterer","Finalize menu: Salad","Finalize menu: Entrée options","Finalize menu: Dessert","Dietary accommodations","Beverage plan","Service timing aligned to program","Final count deadline"]}, {t:"Floor Plan & Table Management",items:["Seating layout (round tables)","Table count (~50–60 tables)","Table numbers created & displayed","Place cards (if assigned seating)","Reserved VIP tables","Master seating chart","Head table: Podium","Head table: Name placards","Head table: Water service"]}, {t:"Decor & Ambience",items:["Orchid-themed centerpieces","Linens & napkins","Chair covers/sashes","Stage décor","Lighting design","Entrance: Welcome signage","Entrance: Step-and-repeat","Entrance: Red carpet (optional)"]}, {t:"Program & Show Flow",items:["Run-of-show (minute-by-minute)","Segment: Cocktail hour","Segment: Dinner","Segment: Awards","Segment: Artist performance","Transition timing"]}, {t:"Awards Management",items:["Award categories finalized","Recipients confirmed","Awards ordered & engraved","Scripts prepared","Presenters assigned","Stage runners assigned","Awards organized backstage in order"]}, {t:"Speakers / Presenters",items:["Emcee confirmed","Speaker list finalized","Bios & headshots collected","Talking points provided","Time limits assigned","Rehearsal scheduled","Stage flow planned"]}, {t:"Live Artist",items:["Artist booked","Performance type confirmed","Technical requirements","Stage placement","Performance timing","Contract/payment"]}, {t:"Audio / Visual",items:["Wireless microphones (4–6)","Podium mic","Backup mic","Screens/projectors","Slide deck (awards/sponsors)","Walk-up music","A/V operator assigned","Full rehearsal completed"]}, {t:"Guest Experience",items:["Ticketing system","Guest list tracking","Check-in process","Registration table setup","Programs distributed","Coat check (if needed)"]}, {t:"Print Materials",items:["Program booklet","Seating chart display","Table numbers","Directional signage","Name badges"]}, {t:"Sponsorship",items:["Sponsors confirmed","Sponsorship levels defined","Benefits delivered","Sponsor slides created","Sponsor seating"]}, {t:"Staffing & Volunteers",items:["Registration team","Ushers","Stage assistants","Volunteer assignments","Communication tools","Dress code"]}, {t:"Risk Management",items:["Emergency plan","Medical readiness","Security staff","Crowd control","Vendor insurance verified"]}, {t:"Finance & Admin",items:["Contracts signed","Payment schedule tracked","Budget vs actual tracked","Cash handling plan"]}, {t:"Day-Of Execution",items:["Setup execution","Vendor check-in","A/V tested","Staff briefing","Registration open on time","Program executed smoothly"]}, {t:"Post-Event",items:["Breakdown completed","Final payments issued","Thank-you communications","Post-event report","Lessons learned"]} ]}, necrology:{sections:[ {t:"Purpose, Tone & Oversight",items:["Define purpose of session (honoring deceased brothers/sisters)","Establish tone: Reverent","Establish tone: Dignified","Establish tone: Spiritually grounded","Confirm overall session flow and duration","Assign Presiding Officer/Chaplain","Assign Program Coordinator","Assign A/V Lead","Assign Protocol/Family Liaison","Assign Floor Manager"]}, {t:"Program Structure & Content",items:["Develop run-of-show: Opening scripture","Develop run-of-show: Prayer/Invocation","Develop run-of-show: Musical selection","Develop run-of-show: Reading of names (Necrology Roll)","Develop run-of-show: Moment of silence","Develop run-of-show: Reflections/tributes","Develop run-of-show: Closing prayer/benediction","Confirm order of service is appropriate and respectful","Assign speakers/readers for each segment","Set time limits"]}, {t:"Necrology Roll (Critical Component)",items:["Compile official list of deceased members","Verify correct spelling of names","Verify chapter affiliations","Confirm final list approval","Determine reading method: Individual or Group reading","Prepare printed copy for reader","Provide backup copy"]}, {t:"Seating & Room Setup",items:["Arrange seating: Reserved front section (families, leadership)","Arrange seating: General seating","Ensure clear center aisle","Ensure comfortable spacing","Place reserved signage","Maintain quiet, respectful environment"]}, {t:"Symbolic Elements & Memorial Setup",items:["Prepare memorial display table or altar","Photos (if available)","Setup symbolic elements: Candles (real or LED)","Setup symbolic elements: Floral arrangement","Confirm safety compliance for candles","Ensure visibility to audience"]}, {t:"Music & Worship Elements",items:["Select music: Opening selection","Select music: Reflection music","Confirm choir, soloist, or recorded music","Conduct sound check","Cue music at proper transitions","Maintain soft background music where appropriate"]}, {t:"Speakers & Participants",items:["Confirm Chaplain/Presiding Officer","Confirm Scripture reader","Confirm Prayer leader","Confirm Reflection speaker(s)","Provide guidance on tone and time limits","Position speakers near stage","Assign mic runner"]}, {t:"Audio / Visual Control",items:["Setup podium microphone","Setup wireless microphones","Prepare backup microphones","Load name slides visual presentation","Load photo tribute (if used)","Test sound levels","Test music playback","Maintain low, respectful volume levels"]}, {t:"Flow & Atmosphere Control",items:["Ensure quiet entry and seating","Minimize movement during session","Maintain respectful environment","Manage late arrivals discreetly","Ensure smooth transitions between segments"]}, {t:"Risk Management & Contingency",items:["Backup reader for necrology roll","Printed copies of all materials","Backup A/V plan","Plan for: Emotional moments","Plan for: Participant absence","Assign support contact for families"]}, {t:"Session Execution Check",items:["Room set and prepared","Memorial display complete","A/V tested and ready","Speakers present and prepared","Necrology list verified","Audience seated respectfully","Program executed smoothly","Tone remains dignified throughout"]} ]} }; // Workshop sections (shared template) function makeWS(label){return {sections:[ {t:`Workshop Strategy & Objectives`,items:["Define workshop purpose and learning outcomes","Identify target audience (delegates, students, leaders)","Format: Lecture","Format: Interactive","Format: Panel discussion","Format: Breakout sessions","Define success metrics","Confirm workshop duration and structure"]}, {t:"Facilitator / Presenter Management",items:["Confirm facilitator(s)","Collect: Bio","Collect: Headshot","Collect: Presentation materials","Align on: Content objectives","Align on: Time limits","Align on: Engagement strategy","Provide conference expectations: Audience level","Provide conference expectations: Room setup","Schedule briefing/rehearsal"]}, {t:"Room & Space Setup",items:["Confirm room assignment and capacity","Layout: Classroom style","Layout: Theater style","Layout: Round tables","Layout: U-shape","Arrange tables and chairs","Arrange facilitator table/podium","Ensure clear sightlines","Ensure accessible entry/exit paths","Post signage outside room"]}, {t:"Seating & Flow Management",items:["Determine open vs reserved seating","Assign ushers (if needed)","Fill front seats first","Maintain clear aisles","Manage late arrivals quietly"]}, {t:"Audio / Visual Setup",items:["Provide microphone (lapel or handheld)","Provide backup microphone","Setup projector or screen","Setup laptop connection (HDMI/adapters)","Load presentation onto master system","Test: Slides","Test: Audio/video playback","Assign A/V support person"]}, {t:"Materials & Content Management",items:["Collect final presentation slides","Prepare handouts (if applicable)","Provide notepads","Provide pens","Upload materials to shared platform (optional)","Prepare backup copies"]}, {t:"Program Flow & Time Control",items:["Session outline: Introduction","Session outline: Main content","Session outline: Interactive segment","Session outline: Q&A","Assign moderator (if needed)","Set time limits per section","Assign timekeeper","Build buffer time for transitions"]}, {t:"Participant Engagement Logistics",items:["Plan engagement: Q&A","Plan engagement: Group discussions","Plan engagement: Polls","Prepare instructions for participants","Assign facilitator support (for breakout groups)","Ensure materials for activities are ready"]}, {t:"Rehearsal & Tech Check",items:["Conduct facilitator briefing","Test A/V equipment","Run through presentation flow","Confirm slide transitions","Confirm video/audio playback","Practice timing"]}, {t:"Registration / Attendance (If Required)",items:["Setup sign-in sheet or digital check-in","Track attendance","Distribute materials at entry","Manage capacity limits"]}, {t:"Communication & Promotion",items:["Include workshop in conference agenda","Promote via: Email","Promote via: App","Promote via: Announcements","Provide room location","Provide session description","Send reminders to attendees"]}, {t:"Risk Management & Contingency",items:["Backup facilitator identified","Backup presentation available","Extra equipment: Cables","Extra equipment: Adapters","Plan for: A/V failure","Plan for: Low attendance","Plan for: Overcapacity","Assign escalation contact"]}, {t:"Session Execution",items:["Room setup completed","A/V tested and active","Facilitator present and ready","Materials distributed","Session starts on time","Engagement maintained","Time managed effectively"]}, {t:"Transition & Closeout",items:["Announce next session/location","Collect materials (if reusable)","Reset room for next session","Thank facilitator and participants"]} ]};} TABS_DATA.workshop1=makeWS('Workshop 1'); TABS_DATA.workshop2=makeWS('Workshop 2'); TABS_DATA.workshop3=makeWS('Workshop 3'); const INSURANCE_ITEMS=["Confirm insurance requirement with venue","Obtain general liability policy","Obtain event cancellation insurance","Obtain liquor liability (if applicable)","Add venue as additional insured","Submit proof of insurance to venue","Confirm vendor insurance certificates","Review policy limits and exclusions","Keep copies of all policies on-site","Brief leadership on emergency protocols"]; // ═══════════ STATE ═══════════ let state={checkboxes:{},remarks:{},remarksList:{},dueDates:{},deletedTasks:{},assigned:{},notes:{},attachments:{},greetings:[],tourStops:[],vendors:[],volunteers:[],dignitaries:[],pocs:[],planningPocs:[],addedTasks:{},customTabs:[],budgetIncome:[],budgetExpense:[],sponsors:[],exhibitors:[],meetings:[],contracts:[],resources:[],agendas:[],hotels:[],committeeMembers:{},chairAddedTasks:{}}; // ═══════════ RENDER SECTIONS ═══════════ function renderSections(tabId,sections){ const c=document.getElementById('sections-'+tabId); if(!c)return; c.innerHTML=''; sections.forEach((sec,si)=>{ const card=document.createElement('div');card.className='section-card'; const hdr=document.createElement('div');hdr.className='section-header'; hdr.innerHTML=`

    ${sec.t}

    `; hdr.addEventListener('click',()=>{ const b=card.querySelector('.section-body'); b.classList.toggle('collapsed'); hdr.querySelector('.toggle-icon').textContent=b.classList.contains('collapsed')?'▶':'▼'; }); const body=document.createElement('div');body.className='section-body'; // Assigned To + Due Date const ak=`${tabId}-s${si}-assigned`; const dk=`${tabId}-s${si}-duedate`; const mr=document.createElement('div');mr.className='meta-row'; mr.innerHTML=`
    `; mr.querySelector('input[type="text"]').addEventListener('input',e=>{state.assigned[e.target.dataset.key]=e.target.value;}); mr.querySelector('input[type="date"]').addEventListener('input',e=>{if(!state.dueDates)state.dueDates={};state.dueDates[e.target.dataset.dkey]=e.target.value;}); body.appendChild(mr); // Items const ul=document.createElement('ul');ul.className='checklist'; const baseItems=sec.items.filter((_,idx)=>{ const tkey=`${tabId}-${si}-${idx}`; return !(state.deletedTasks&&state.deletedTasks[tkey]); }); const baseItemsWithOrigIdx=sec.items.map((item,idx)=>({item,origIdx:idx,isCustom:false})) .filter((_,idx)=>!(state.deletedTasks&&state.deletedTasks[`${tabId}-${si}-${idx}`])); const customItems=(state.addedTasks[`${tabId}-s${si}`]||[]).map((item,ci)=>({item,origIdx:sec.items.length+ci,isCustom:true})); const allItems=[...baseItemsWithOrigIdx,...customItems]; allItems.forEach(({item,origIdx,isCustom})=>{ ul.appendChild(renderCheckItem(tabId,si,origIdx,item,isCustom)); }); body.appendChild(ul); // Add task const addRow=document.createElement('div');addRow.className='add-task-row no-print'; addRow.innerHTML=``; body.appendChild(addRow); card.appendChild(hdr);card.appendChild(body);c.appendChild(card); }); updateProgress(tabId); } function renderCheckItem(tabId,si,ii,text,isCustom){ const li=document.createElement('li');li.className='check-item'; const key=`${tabId}-${si}-${ii}`; const checked=state.checkboxes[key]||false; const savedDue=state.dueDates&&state.dueDates[key]?state.dueDates[key]:''; const savedAsgn=state.assigned&&state.assigned[key]?state.assigned[key]:''; const isOverdue=savedDue&&!checked&&new Date(savedDue+'T00:00:00')
    ${isOverdue?'OVERDUE':isDueSoon?'DUE SOON':''}
    Remarks:
    `; li.querySelector('input[type="checkbox"]').addEventListener('change',e=>{ state.checkboxes[key]=e.target.checked; updateProgress(tabId); // Refresh due color indicators when checked/unchecked const dueEl=document.getElementById('due-'+key); if(dueEl) pbsRefreshDueColors(dueEl,key); }); renderRemarksList(key); return li; } function pbsRefreshDueColors(inputEl,key){ if(!inputEl) return; const val=inputEl.value; const checked=state.checkboxes&&state.checkboxes[key]; if(!val||checked){ inputEl.style.borderColor='rgba(26,82,196,.2)'; inputEl.style.color='var(--royal-dark)'; inputEl.style.background='#f8faff'; // Remove overdue/due-soon badges const badges=inputEl.closest('.check-item-meta')&&inputEl.closest('.check-item-meta').querySelectorAll('span[style*="border-radius:10px"]'); if(badges) badges.forEach(function(b){b.remove();}); return; } const now=new Date(); const due=new Date(val+'T00:00:00'); const isOverdue=due`
    ${r.time} ${esc(r.text)}
    `).join(''); } function saveRemark(key,tabId){ const row=document.querySelector(`.remarks-add-row input[data-rkey="${key}"]`); if(!row)return; const text=row.value.trim();if(!text)return; if(!state.remarksList)state.remarksList={}; if(!state.remarksList[key])state.remarksList[key]=[]; const now=new Date(); const time=now.toLocaleDateString('en-US',{month:'short',day:'numeric'})+' '+now.toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit'}); state.remarksList[key].unshift({text,time}); // newest on top row.value=''; renderRemarksList(key); } function deleteRemark(key,i){ if(!state.remarksList||!state.remarksList[key])return; state.remarksList[key].splice(i,1); renderRemarksList(key); } function deleteTask(tabId,si,ii){ deleteAnyTask(tabId,si,ii); } function deleteAnyTask(tabId,si,ii){ const sectionKey=`${tabId}-s${si}`; const sections=TABS_DATA[tabId].sections; const baseCount=sections[si].items.length; if(ii/g,'>');} // ── CURRENCY FORMATTING ── function formatCurrency(val) { if (!val && val !== 0) return ''; var num = parseFloat(String(val).replace(/[^0-9.-]/g, '')); if (isNaN(num)) return val; return '$' + num.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ','); } function parseCurrency(val) { if (!val) return 0; var num = parseFloat(String(val).replace(/[^0-9.-]/g, '')); return isNaN(num) ? 0 : num; } function attachCurrencyFormatting() { // Event delegation - catches ALL inputs including dynamically created ones document.addEventListener('blur', function(e) { var el = e.target; if (!el || el.tagName !== 'INPUT') return; if (!isCurrencyField(el)) return; var val = el.value.trim(); if (!val) return; var raw = parseFloat(val.replace(/[^0-9.-]/g, '')); if (!isNaN(raw)) { el.value = '$' + raw.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ','); } }, true); // Strip formatting on focus so user can type freely document.addEventListener('focus', function(e) { var el = e.target; if (!el || el.tagName !== 'INPUT') return; if (!isCurrencyField(el)) return; var val = el.value.trim(); if (val.startsWith('$')) { el.value = val.replace(/[^0-9.-]/g, ''); } }, true); } function isCurrencyField(el) { if (!el) return false; if (el.classList.contains('currency-field')) return true; if (el.dataset && el.dataset.currency) return true; var ph = el.placeholder || ''; if (ph === '$0.00' || ph === '$') return true; // Check by field ID patterns var id = el.id || ''; if (/amount|budget|revenue|fee|rate|price|cost|value|income|expense|sponsor|tier/i.test(id)) return true; // Check by name attribute var name = el.name || ''; if (/amount|budget|revenue|fee|rate|price|cost|value/i.test(name)) return true; return false; } // Format all existing currency fields on the page (called after renders) function formatAllCurrencyFields() { document.querySelectorAll('input[placeholder="$0.00"], input[placeholder="$"]').forEach(function(el) { var val = el.value.trim(); if (!val || val.startsWith('$')) return; var raw = parseFloat(val.replace(/[^0-9.-]/g, '')); if (!isNaN(raw) && raw !== 0) { el.value = '$' + raw.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ','); } }); } function openStoredFile(dataUrl, filename) { if (!dataUrl) { showToast('File not available. Please re-upload this file.','error'); return; } try { var a = document.createElement('a'); a.href = dataUrl; a.target = '_blank'; a.download = filename || 'file'; document.body.appendChild(a); a.click(); document.body.removeChild(a); } catch(e) { showToast('Could not open file: ' + e.message, 'error'); } } function downloadStoredFile(dataUrl, filename) { if (!dataUrl) { showToast('File data not found. Please re-upload.','error'); return; } var a = document.createElement('a'); a.href = dataUrl; a.download = filename || 'download'; document.body.appendChild(a); a.click(); document.body.removeChild(a); } function openContractFile(ci, fi) { var f = state.contracts[ci] && state.contracts[ci].files && state.contracts[ci].files[fi]; if (f) openStoredFile(f.dataUrl, f.name); } function downloadContractFile(ci, fi) { var f = state.contracts[ci] && state.contracts[ci].files && state.contracts[ci].files[fi]; if (f) downloadStoredFile(f.dataUrl, f.name); } function openAttachmentFile(tabId, i) { var a = state.attachments[tabId] && state.attachments[tabId][i]; if (a) openStoredFile(a.dataUrl, a.name); } function downloadAttachmentFile(tabId, i) { var a = state.attachments[tabId] && state.attachments[tabId][i]; if (a) downloadStoredFile(a.dataUrl, a.name); } // ═══════════ INSURANCE ═══════════ function renderInsuranceChecklist(){ const ul=document.getElementById('insurance-checklist');if(!ul)return; ul.innerHTML=''; INSURANCE_ITEMS.forEach((item,i)=>{ const key=`insurance-item-${i}`; if(!state.remarksList)state.remarksList={}; const li=document.createElement('li');li.className='check-item'; li.innerHTML=`
    Remarks:
    `; li.querySelector('input[type="checkbox"]').addEventListener('change',e=>{state.checkboxes[key]=e.target.checked;}); ul.appendChild(li); renderRemarksList(key); }); } // ═══════════ GREETINGS ═══════════ function renderGreetings(){ const list=document.getElementById('greetings-list');if(!list)return; list.innerHTML=''; state.greetings.forEach((g,i)=>{ const card=document.createElement('div');card.className='greeting-card'; card.innerHTML=`
    `; list.appendChild(card); }); } function addGreeting(){state.greetings.push({from:'',title:'',date:'',content:''});renderGreetings();} function removeGreeting(i){state.greetings.splice(i,1);renderGreetings();} // ═══════════ TOUR ═══════════ function renderTour(){ const list=document.getElementById('tour-list');if(!list)return;list.innerHTML=''; state.tourStops.forEach((s,i)=>{ const item=document.createElement('div');item.className='tour-item'; item.innerHTML=`
    ${i+1}
    `; list.appendChild(item); }); } function addTourStop(){state.tourStops.push({area:'',notes:''});renderTour();} function removeTourStop(i){state.tourStops.splice(i,1);renderTour();} // ═══════════ TABLES ═══════════ function mkSel(vals,cur,cb){return ``;} function addVendorRow(){state.vendors.push({name:'',cat:'',contact:'',info:'',contract:'No',setup:'',fee:'',status:'Pending',notes:''});renderVendors();} function renderVendors(){ const tb=document.getElementById('vendor-tbody');if(!tb)return;tb.innerHTML=''; state.vendors.forEach((v,i)=>{ const tr=document.createElement('tr'); tr.innerHTML=` ${mkSel(['No','Yes','Pending'],v.contract,`state.vendors[${i}].contract=this.value`)} ${mkSel(['Pending','Confirmed','Cancelled','On-Site','Departed'],v.status,`state.vendors[${i}].status=this.value`)} `; tb.appendChild(tr); }); try{formatAllCurrencyFields();}catch(e){} } function addVolunteerRow(){state.volunteers.push({name:'',role:'',phone:'',email:'',shift:'',status:'Active'});renderVolunteers();} function renderVolunteers(){ const tb=document.getElementById('volunteer-tbody');if(!tb)return;tb.innerHTML=''; state.volunteers.forEach((v,i)=>{ const tr=document.createElement('tr'); tr.innerHTML=`${mkSel(['Active','Checked-In','No-Show','Completed'],v.status,`state.volunteers[${i}].status=this.value`)}`; tb.appendChild(tr); }); } function addDignitary(){state.dignitaries.push({name:'',title:'',role:'',seating:'',escort:'',needs:''});renderDignitaries();} function renderDignitaries(){ const tb=document.getElementById('dignitary-tbody');if(!tb)return;tb.innerHTML=''; state.dignitaries.forEach((d,i)=>{ const tr=document.createElement('tr'); tr.innerHTML=``; tb.appendChild(tr); }); } function addPocRow(){state.pocs.push({name:'',title:'',dept:'',phone:'',email:'',avail:'',notes:''});renderPocs();} function addPlanningPocRow(){ if(!state.planningPocs)state.planningPocs=[]; state.planningPocs.push({name:'',title:'',committee:'',phone:'',email:'',avail:'',responsibilities:'',notes:''}); renderPlanningPocs(); } function renderPlanningPocs(){ const tb=document.getElementById('planningpoc-tbody');if(!tb)return;tb.innerHTML=''; if(!state.planningPocs)state.planningPocs=[]; state.planningPocs.forEach((p,i)=>{ const tr=document.createElement('tr'); tr.innerHTML=` `; tb.appendChild(tr); }); } function renderPocs(){ const tb=document.getElementById('poc-tbody');if(!tb)return;tb.innerHTML=''; state.pocs.forEach((p,i)=>{ const tr=document.createElement('tr'); tr.innerHTML=``; tb.appendChild(tr); }); } // ═══════════ ATTACHMENTS ═══════════ function addAttachment(input,tabId){ const files=Array.from(input.files); if(!state.attachments[tabId])state.attachments[tabId]=[]; files.forEach(f=>{ const idx=state.attachments[tabId].length; state.attachments[tabId].push({name:f.name,date:new Date().toLocaleDateString(),size:fmtSize(f.size),dataUrl:null}); const reader=new FileReader(); reader.onload=e=>{ if(state.attachments[tabId]&&state.attachments[tabId][idx]){ state.attachments[tabId][idx].dataUrl=e.target.result; renderAttachments(tabId); } }; reader.readAsDataURL(f); }); renderAttachments(tabId); input.value=''; } function renderAttachments(tabId){ const c=document.getElementById('attach-'+tabId);if(!c)return; const items=state.attachments[tabId]||[]; if(!items.length){c.innerHTML='No attachments yet.';return;} c.innerHTML=items.map((a,i)=>{ const hasData=!!a.dataUrl; const openBtns=hasData ? ` ` : 'Re-upload to open'; return `
    📄${esc(a.name)}${a.date} · ${a.size}${openBtns}
    `; }).join(''); } function removeAttachment(tabId,i){state.attachments[tabId].splice(i,1);renderAttachments(tabId);} function fmtSize(b){if(b<1024)return b+' B';if(b<1048576)return (b/1024).toFixed(1)+' KB';return (b/1048576).toFixed(1)+' MB';} // ═══════════ CUSTOM EVENTS / TABS ═══════════ function addCustomEvent(){ const inp=document.getElementById('new-event-name'); const name=inp.value.trim();if(!name)return; const id='custom-'+Date.now(); state.customTabs.push({id,name}); inp.value=''; renderCustomNav(); createCustomTab(id,name); } function renderCustomNav(){ const c=document.getElementById('custom-nav-items');c.innerHTML=''; state.customTabs.forEach(t=>{ const btn=document.createElement('button');btn.className='nav-btn';btn.id='nav-'+t.id; btn.style.display='flex';btn.style.alignItems='center';btn.style.justifyContent='space-between'; const spanLabel=document.createElement('span');spanLabel.textContent='📌 '+t.name;spanLabel.onclick=function(){showTab(t.id);}; const spanDel=document.createElement('span');spanDel.textContent='✕';spanDel.className='delete-tab-btn no-print';spanDel.style.cssText='margin-left:6px;font-size:.65rem;padding:1px 6px'; spanDel.onclick=function(e){e.stopPropagation();deleteCustomTab(t.id);}; btn.appendChild(spanLabel);btn.appendChild(spanDel); btn.onclick=function(e){if(!e.target.classList.contains('delete-tab-btn'))showTab(t.id);}; c.appendChild(btn); }); } function createCustomTab(id,name){ const c=document.getElementById('custom-tabs-container'); const div=document.createElement('div');div.className='tab-panel';div.id='tab-'+id; div.innerHTML=`

    📌 ${esc(name)}

    Custom Event Tab

    Event Details

    Custom Checklist

      📝 Notes
      📎 Attachments
      `; c.appendChild(div); } function addCustomTask(tabId){ const inp=document.getElementById('custom-inp-'+tabId); const text=inp.value.trim();if(!text)return; const ul=document.getElementById('custom-ul-'+tabId); const li=document.createElement('li');li.className='check-item'; const key='custom-task-'+Date.now(); li.innerHTML=`
      Remarks:
      `; ul.appendChild(li);inp.value=''; } // ═══════════ BUDGET ═══════════ function addBudgetRow(type){ if(type==='income') state.budgetIncome.push({cat:'',desc:'',est:'',actual:'',notes:''}); else state.budgetExpense.push({cat:'',desc:'',est:'',actual:'',vendor:'',status:'Pending',notes:''}); renderContracts(); renderPlanningPocs(); renderBudget(); } function renderBudget(){ const itb=document.getElementById('budget-income-tbody'); const etb=document.getElementById('budget-expense-tbody'); const sum=document.getElementById('budget-summary'); if(!itb||!etb)return; itb.innerHTML=''; state.budgetIncome.forEach((r,i)=>{ const tr=document.createElement('tr'); tr.innerHTML=``; itb.appendChild(tr); }); etb.innerHTML=''; state.budgetExpense.forEach((r,i)=>{ const tr=document.createElement('tr'); tr.innerHTML=`${mkSel(['Pending','Paid','Partial','Overdue'],r.status,`state.budgetExpense[${i}].status=this.value`)}`; etb.appendChild(tr); }); updateBudgetSummary(); try{formatAllCurrencyFields();}catch(e){} } function parseMoney(s){const n=parseFloat(String(s||'').replace(/[$,]/g,''));return isNaN(n)?0:n;} function fmtMoney(n){return '$'+n.toLocaleString('en-US',{minimumFractionDigits:2,maximumFractionDigits:2});} function fmtMoney(n){return '$'+n.toLocaleString('en-US',{minimumFractionDigits:2,maximumFractionDigits:2});} function updateBudgetSummary(){ const s=document.getElementById('budget-summary');if(!s)return; const totalIncEst=state.budgetIncome.reduce((a,r)=>a+parseMoney(r.est),0); const totalIncAct=state.budgetIncome.reduce((a,r)=>a+parseMoney(r.actual),0); const totalExpEst=state.budgetExpense.reduce((a,r)=>a+parseMoney(r.est),0); const totalExpAct=state.budgetExpense.reduce((a,r)=>a+parseMoney(r.actual),0); const netEst=totalIncEst-totalExpEst;const netAct=totalIncAct-totalExpAct; s.innerHTML=`
      Total Income (Est.)
      ${fmtMoney(totalIncEst)}
      Total Income (Actual)
      ${fmtMoney(totalIncAct)}
      Total Expenses (Est.)
      ${fmtMoney(totalExpEst)}
      Total Expenses (Actual)
      ${fmtMoney(totalExpAct)}
      Net Balance (Est.)
      ${fmtMoney(netEst)}
      Net Balance (Actual)
      ${fmtMoney(netAct)}
      `; } // ═══════════ SPONSORS ═══════════ function addSponsorRow(){state.sponsors.push({org:'',contact:'',phone:'',email:'',level:'Gold',amount:'',benefits:'No',status:'Prospect',notes:''});renderSponsors();} function renderSponsors(){ const tb=document.getElementById('sponsor-tbody');if(!tb)return;tb.innerHTML=''; state.sponsors.forEach((s,i)=>{ const tr=document.createElement('tr'); tr.innerHTML=`${mkSel(['Platinum','Gold','Silver','Bronze','In-Kind'],s.level,`state.sponsors[${i}].level=this.value`)}${mkSel(['Yes','Partial','No'],s.benefits,`state.sponsors[${i}].benefits=this.value`)}${mkSel(['Prospect','Contacted','Committed','Received','Declined'],s.status,`state.sponsors[${i}].status=this.value`)}`; tb.appendChild(tr); }); try{formatAllCurrencyFields();}catch(e){} } // ═══════════ EXHIBITORS ═══════════ function addExhibitorRow(){state.exhibitors.push({org:'',contact:'',info:'',booth:'',cat:'',fee:'',setup:'',ins:'No',status:'Pending',notes:''});renderExhibitors();} function renderExhibitors(){ const tb=document.getElementById('exhibitor-tbody');if(!tb)return;tb.innerHTML=''; state.exhibitors.forEach((e,i)=>{ const tr=document.createElement('tr'); tr.innerHTML=` ${mkSel(['Yes','No','Pending'],e.ins,`state.exhibitors[${i}].ins=this.value`)} ${mkSel(['Pending','Confirmed','Cancelled','On-Site'],e.status,`state.exhibitors[${i}].status=this.value`)} `; tb.appendChild(tr); }); try{formatAllCurrencyFields();}catch(e){} } // ═══════════ MEETINGS CALENDAR ═══════════ let calYear=new Date().getFullYear();let calMonth=new Date().getMonth(); const MONTHS=['January','February','March','April','May','June','July','August','September','October','November','December']; const DAYS=['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; function addMeeting(){ const today=new Date().toISOString().split('T')[0]; state.meetings.push({title:'',date:today,time:'',location:'',attendees:'',agenda:'',notes:''}); renderMeetings();renderCalendar(); } function removeMeeting(i){state.meetings.splice(i,1);renderMeetings();renderCalendar();} function renderMeetings(){ const c=document.getElementById('meetings-list');if(!c)return; if(!state.meetings.length){c.innerHTML='

      No meetings scheduled. Click "+ Add Meeting" to get started.

      ';return;} const sorted=[...state.meetings].map((m,i)=>({...m,idx:i})).sort((a,b)=>new Date(a.date)-new Date(b.date)); c.innerHTML=''; sorted.forEach(m=>{ const i=m.idx; const d=m.date?new Date(m.date+'T12:00:00'):new Date(); const mon=MONTHS[d.getMonth()].slice(0,3).toUpperCase(); const day=d.getDate(); const card=document.createElement('div');card.className='meeting-card'; card.innerHTML=`
      ${mon}
      ${day}
      `; c.appendChild(card); }); } function renderCalendar(){ const lbls=document.getElementById('cal-day-labels'); const grid=document.getElementById('cal-grid'); const label=document.getElementById('cal-month-label'); if(!grid)return; if(lbls&&!lbls.children.length){DAYS.forEach(d=>{const el=document.createElement('div');el.className='cal-day-label';el.textContent=d;lbls.appendChild(el);});} if(label)label.textContent=`${MONTHS[calMonth]} ${calYear}`; grid.innerHTML=''; const firstDay=new Date(calYear,calMonth,1).getDay(); const daysInMonth=new Date(calYear,calMonth+1,0).getDate(); const daysInPrev=new Date(calYear,calMonth,0).getDate(); const today=new Date(); // Prev month filler for(let i=firstDay-1;i>=0;i--){const d=document.createElement('div');d.className='cal-day other-month';d.innerHTML=`
      ${daysInPrev-i}
      `;grid.appendChild(d);} // Current month for(let day=1;day<=daysInMonth;day++){ const d=document.createElement('div');d.className='cal-day'; const isToday=today.getFullYear()===calYear&&today.getMonth()===calMonth&&today.getDate()===day; if(isToday)d.classList.add('today'); const dateStr=`${calYear}-${String(calMonth+1).padStart(2,'0')}-${String(day).padStart(2,'0')}`; const dayMeetings=state.meetings.filter(m=>m.date===dateStr); d.innerHTML=`
      ${day}
      ${dayMeetings.map(m=>`${esc(m.title||'Meeting')}`).join('')}`; d.addEventListener('click',()=>{ state.meetings.push({title:'',date:dateStr,time:'',location:'',attendees:'',agenda:'',notes:''}); renderMeetings();renderCalendar();showTab('meetings'); setTimeout(()=>{const c=document.getElementById('meetings-list');if(c)c.scrollTop=c.scrollHeight;},100); }); grid.appendChild(d); } // Next month filler const total=firstDay+daysInMonth;const remaining=(7-total%7)%7; for(let i=1;i<=remaining;i++){const d=document.createElement('div');d.className='cal-day other-month';d.innerHTML=`
      ${i}
      `;grid.appendChild(d);} } function calPrev(){calMonth--;if(calMonth<0){calMonth=11;calYear--;}renderCalendar();} function calNext(){calMonth++;if(calMonth>11){calMonth=0;calYear++;}renderCalendar();} // ═══════════ CONTRACT REPOSITORY ═══════════ const CONTRACT_CATEGORIES = ['Venue','Catering','Audio/Visual','Entertainment','Photography/Video','Security','Décor','Transportation','Insurance','Printing','Sponsorship','Volunteer','Speaker','Other']; const CONTRACT_STATUSES = ['Draft','Pending Signature','Active','Executed','Expired','Cancelled']; function addContractEntry(){ state.contracts.push({ title:'', category:'Venue', vendor:'', contactName:'', contactEmail:'', contactPhone:'', value:'', startDate:'', endDate:'', signedDate:'', status:'Pending Signature', description:'', notes:'', files:[] }); renderContracts(); } function removeContract(i){ if(confirm('Remove this contract entry?')){state.contracts.splice(i,1);renderContracts();} } function renderContracts(){ const list=document.getElementById('contract-list'); const summaryBar=document.getElementById('contract-summary-bar'); if(!list)return; // Summary counts const total=state.contracts.length; const active=state.contracts.filter(c=>c.status==='Active'||c.status==='Executed').length; const pending=state.contracts.filter(c=>c.status==='Pending Signature'||c.status==='Draft').length; const expired=state.contracts.filter(c=>c.status==='Expired'||c.status==='Cancelled').length; const totalVal=state.contracts.reduce((a,c)=>{const n=parseFloat(String(c.value||'').replace(/[$,]/g,''));return a+(isNaN(n)?0:n);},0); if(summaryBar){summaryBar.innerHTML=`
      ${total}
      Total Contracts
      ${active}
      Active / Executed
      ${pending}
      Pending / Draft
      ${expired}
      Expired / Cancelled
      ${fmtMoney(totalVal)}
      Total Contract Value
      `;} if(!total){ list.innerHTML=`
      📁

      No contracts yet

      Click "+ Add Contract" to begin building your repository.

      `; return; } list.innerHTML=''; state.contracts.forEach((c,i)=>{ const statusClass={'Draft':'status-draft','Pending Signature':'status-pending','Active':'status-active','Executed':'status-active','Expired':'status-expired','Cancelled':'status-expired'}[c.status]||'status-draft'; const catIcons={'Venue':'🏛️','Catering':'🍽️','Audio/Visual':'🔊','Entertainment':'🎤','Photography/Video':'📸','Security':'🛡️','Décor':'🌺','Transportation':'🚌','Insurance':'📋','Printing':'🖨️','Sponsorship':'🤝','Speaker':'🎙️','Volunteer':'👥','Other':'📄'}; const icon=catIcons[c.category]||'📄'; const catOptions=CONTRACT_CATEGORIES.map(cat=>``).join(''); const statusOptions=CONTRACT_STATUSES.map(s=>``).join(''); const filesHtml=c.files&&c.files.length?c.files.map((f,fi)=>{ const hasData=!!f.dataUrl; const icon=f.name.endsWith('.pdf')?'📄':f.name.match(/\.(doc|docx)$/)?'📋':f.name.match(/\.(xls|xlsx)$/)?'📊':'📎'; const btns=hasData ? ` ` : 'Re-upload to access'; return `
      ${icon}${esc(f.name)} ${f.size}${f.date} ${btns}
      `; }).join(''):'

      No files uploaded yet.

      '; const card=document.createElement('div');card.className='contract-card'; card.innerHTML=`
      ${icon}
      ${c.status}
      📎 Uploaded Contract Files
      ${filesHtml}
      `; list.appendChild(card); }); } function uploadContractFile(input,contractIdx){ const files=Array.from(input.files); if(!state.contracts[contractIdx].files)state.contracts[contractIdx].files=[]; files.forEach(f=>{ state.contracts[contractIdx].files.push({ name:f.name, size:fmtSize(f.size), date:new Date().toLocaleDateString(), dataUrl:null }); // Store file content as dataUrl for download const reader=new FileReader(); const idx=state.contracts[contractIdx].files.length-1; reader.onload=e=>{state.contracts[contractIdx].files[idx].dataUrl=e.target.result;setTimeout(()=>renderContracts(),100);}; reader.readAsDataURL(f); }); input.value=''; setTimeout(()=>renderContracts(),200); } function removeContractFile(contractIdx,fileIdx){ state.contracts[contractIdx].files.splice(fileIdx,1); renderContracts(); } // ═══════════ OVERVIEW & REPORT ═══════════ const tabNames={virtual:'Virtual Session',friday:'Friday Operations Day',opening:'Opening Ceremony',pageant:'Pageant',stepshow:'Step Show',luncheon:'1914 Luncheon',workshop1:'Workshop 1',workshop2:'Workshop 2',workshop3:'Workshop 3',orchid:'Orchid Ball',necrology:'Necrology'}; function refreshOverview(){ var tNames={statedir:'State Director',state1vp:'State 1st Vice Director',statecos:'State Chief of Staff',virtual:'Virtual Session',friday:'Friday Operations Day',opening:'Opening Ceremony',pageant:'Pageant',stepshow:'Step Show',luncheon:'1914 Luncheon',workshop1:'Workshop 1',workshop2:'Workshop 2',workshop3:'Workshop 3',orchid:'Orchid Ball',necrology:'Necrology',regchair:'Registration Chair',techchair:'Technology Chair',securitychair:'Security Chair',sarmschair:'Sgt-at-Arms Chair',electionschair:'Elections Chair',volcoordchair:'Volunteer Coord.',progchair:'Program Chair',ossrchair:'OSSR Chair',vipchair:'VIP & Protocol Chair',awardschair:'Awards Chair',socialchair:'Social Events Chair',necrochair:'Necrology Chair',publicitychair:'Publicity Chair',socialmediachair:'Social Media Chair',journalchair:'Journal Chair',chaprelchair:'Chapter Relations',sponsfundchair:'Sponsorship Chair',vendexhibchair:'Vendor/Exhibitor Chair',commservchair:'Community Service',medicalchair:'Medical Team Chair',hospchair:'Hospitality Chair'}; var ids=Object.keys(tNames); var tc=0,ta=0,stats=[]; ids.forEach(function(tid){ var all=document.querySelectorAll('#sections-'+tid+' input[type="checkbox"]'); var done=document.querySelectorAll('#sections-'+tid+' input[type="checkbox"]:checked'); tc+=done.length; ta+=all.length; stats.push({tab:tid,name:tNames[tid],done:done.length,total:all.length,pct:all.length?Math.round(done.length/all.length*100):0}); }); var opct=ta?Math.round(tc/ta*100):0; var grid=document.getElementById('stats-grid'); if(!grid)return; var h=''; stats.forEach(function(s){ h+='
      ' +'
      '+s.name+'
      ' +'
      '+s.pct+'%
      ' +'
      '+s.done+' / '+s.total+' Tasks
      ' +'
      ' +'
      '; }); h+='
      ' +'
      Overall Progress
      ' +'
      '+opct+'%
      ' +'
      '+tc+' / '+ta+' Total Tasks
      ' +'
      ' +'
      '; grid.innerHTML=h; var sep='==========================================================='; var dashes='----------------------------------------'; var now=new Date().toLocaleString(); var r='PHI BETA SIGMA - STATE OF GEORGIA CONFERENCE\n' +'MASTER LOGISTICS REPORT\n' +'Generated: '+now+'\n' +sep+'\n\n' +'OVERALL PROGRESS: '+opct+'% ('+tc+'/'+ta+' tasks)\n\n'; stats.forEach(function(s){ r+=s.name.toUpperCase()+'\n'+dashes+'\n'; r+='Completion: '+s.pct+'% ('+s.done+'/'+s.total+' tasks)\n'; var n=document.getElementById('notes-'+s.tab); if(n&&n.value&&n.value.trim())r+='Notes: '+n.value.trim()+'\n'; r+='\n'; }); var extras=[ {id:'overview',n:'Overview'},{id:'greetings',n:'Greetings'},{id:'vendors',n:'Vendors'}, {id:'volunteers',n:'Volunteers'},{id:'dignitaries',n:'Dignitaries'},{id:'insurance',n:'Insurance'}, {id:'tour',n:'Tour'},{id:'poc',n:'Convention Center POCs'},{id:'planningpoc',n:'Planning Committee POCs'}, {id:'chapters',n:'Local Chapters'},{id:'budget',n:'Budget'},{id:'contracts',n:'Contracts'}, {id:'meetings',n:'Meetings'},{id:'sponsors',n:'Sponsors'},{id:'exhibitors',n:'Exhibitors'} ]; extras.forEach(function(t){ var el=document.getElementById('notes-'+t.id); if(el&&el.value&&el.value.trim())r+=t.n.toUpperCase()+'\n'+dashes+'\n'+el.value.trim()+'\n\n'; }); if(state.greetings&&state.greetings.length){ r+='GREETINGS & DIGNITARY LETTERS\n'+dashes+'\n'; state.greetings.forEach(function(g){r+='- '+(g.from||'TBD')+' -- '+(g.title||'')+' ('+(g.date||'TBD')+')\n';}); r+='\n'; } if(state.dignitaries&&state.dignitaries.length){ r+='DIGNITARIES: '+state.dignitaries.length+'\n'+dashes+'\n'; state.dignitaries.forEach(function(d){r+='- '+(d.name||'TBD')+' -- '+(d.title||'')+' | Seating: '+(d.seating||'TBD')+'\n';}); r+='\n'; } if(state.vendors&&state.vendors.length){ r+='VENDORS: '+state.vendors.length+'\n'+dashes+'\n'; state.vendors.forEach(function(v){r+='- '+(v.name||'TBD')+' ['+(v.cat||'')+'] -- '+v.status+'\n';}); r+='\n'; } if(state.volunteers&&state.volunteers.length){ r+='VOLUNTEERS: '+state.volunteers.length+'\n'+dashes+'\n'; state.volunteers.forEach(function(v){r+='- '+(v.name||'TBD')+' -- '+(v.role||'')+' | '+v.status+'\n';}); r+='\n'; } if(state.pocs&&state.pocs.length){ r+='CONVENTION CENTER POCs: '+state.pocs.length+'\n'+dashes+'\n'; state.pocs.forEach(function(p){r+='- '+(p.name||'TBD')+' -- '+(p.title||'')+' | '+(p.phone||'')+'\n';}); r+='\n'; } var ip=document.getElementById('ins-provider'),ipol=document.getElementById('ins-policy'),ia=document.getElementById('ins-amount'); if((ip&&ip.value)||(ipol&&ipol.value)){ r+='INSURANCE\n'+dashes+'\n'; if(ip&&ip.value)r+='Provider: '+ip.value+'\n'; if(ipol&&ipol.value)r+='Policy: '+ipol.value+'\n'; if(ia&&ia.value)r+='Coverage: '+ia.value+'\n'; r+='\n'; } r+=sep+'\nEND OF MASTER LOGISTICS REPORT\n'; var logEl=document.getElementById('master-log-output'); if(logEl)logEl.textContent=r; } // ═══════════ FULL COMBINED PLAN — COMPREHENSIVE REBUILD ═══════════ var FULL_PLAN_STRUCTURE = [ ["State Leadership", [ ["statedir", "State Director", "chair"], ["state1vp", "State 1st Vice Director", "chair"], ["statecos", "State Chief of Staff", "chair"] ]], ["Conference Executive Staff", [ ["confchair", "Conference / Logistics Chair", "chair"], ["financechair", "State Treasurer", "chair"] ]], ["Registration", [ ["regchair", "Registration Committee", "chair"] ]], ["Programs", [ ["virtual", "Conference Virtual Session", "checklist"], ["opening", "Opening Ceremony", "checklist"], ["workshop1", "Workshop 1", "checklist"], ["workshop2", "Workshop 2", "checklist"], ["workshop3", "Workshop 3", "checklist"], ["orchid", "Orchid Ball", "checklist"], ["luncheon", "1914 Luncheon", "checklist"], ["necrology", "Necrology", "checklist"], ["pageant", "Pageant", "checklist"], ["stepshow", "Step Show", "checklist"], ["socialaction", "Social Action", "chair"] ]], ["Finance & Partnerships", [ ["budget", "Budget", "budget"], ["sponsors", "Sponsorship & Fundraising", "sponsors"], ["exhibitors", "Exhibitors", "exhibitors"], ["vendors", "Vendors", "vendors"], ["meetings", "Meetings", "meetings"] ]], ["Support", [ ["volcoordchair", "Volunteer Coordinator", "chair"], ["hospchair", "Hospitality", "chair"], ["dignitaries", "Dignitary Management / VIP", "dignitaries"], ["vipchair", "Protocol", "chair"], ["techchair", "Technology & A/V", "chair"], ["securitychair", "Security / Sergeant-At-Arms", "chair"] ]], ["Local Support", [ ["chapters", "Local Chapters", "checklist"], ["medicalchair", "Medical Team", "chair"] ]], ["Governance", [ ["electionschair", "Elections Committee", "chair"], ["sarmschair", "Nominations Committee", "chair"] ]], ["Communications", [ ["socialmediachair", "Social Media", "chair"], ["publicitychair", "Publicity & Comms", "chair"], ["journalchair", "Souvenir Journal", "chair"], ["chaprelchair", "Chapter Relations", "chair"] ]], ["Awards", [ ["ossrchair", "OSSR", "chair"], ["awardschair", "Awards & Recognition", "chair"] ]], ["Conference Resources", [ ["contracts", "Contract Repository", "contracts"], ["agenda", "Agendas", "notesonly"], ["poc", "Internal Conference POCs", "poc"], ["planningpoc", "External Conference POCs", "planningpoc"], ["resources", "Resources", "notesonly"] ]], ["Logistics", [ ["friday", "Friday Operations", "checklist"], ["insurance", "Insurance", "insurance"], ["tour", "Convention Tour", "tour"], ["hotels", "Hotels", "hotels"] ]] ]; function fpEsc(s) { if (s === null || s === undefined) return ''; return String(s).replace(/&/g,'&').replace(//g,'>'); } function fpSlug(s) { return String(s).toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/(^-|-$)/g,''); } // Pull chair header info (name/chapter/phone/email/status) function fpChairHeader(tabId) { // Chair fields use either tabId-name (committee chairs) or tabId-event-chair (program tabs) var nameEl = document.getElementById(tabId+'-name') || document.getElementById(tabId+'-event-chair'); var chapEl = document.getElementById(tabId+'-chapter') || document.getElementById(tabId+'-event-chair-chapter'); var phoneEl = document.getElementById(tabId+'-phone') || document.getElementById(tabId+'-event-chair-phone'); var emailEl = document.getElementById(tabId+'-email') || document.getElementById(tabId+'-event-chair-email'); var statusEl = document.getElementById(tabId+'-status'); var name = nameEl ? nameEl.value.trim() : ''; // Co-Chair fields use either tabId-cochair-name (committee chairs) or tabId-event-cochair (program tabs) var coNameEl = document.getElementById(tabId+'-cochair-name') || document.getElementById(tabId+'-event-cochair'); var coChapEl = document.getElementById(tabId+'-cochair-chapter') || document.getElementById(tabId+'-event-cochair-chapter'); var coPhoneEl = document.getElementById(tabId+'-cochair-phone') || document.getElementById(tabId+'-event-cochair-phone'); var coEmailEl = document.getElementById(tabId+'-cochair-email') || document.getElementById(tabId+'-event-cochair-email'); var coName = coNameEl ? coNameEl.value.trim() : ''; var html = ''; if (name) { var statusLabel = statusEl ? statusEl.options[statusEl.selectedIndex].text : ''; var bits = ['Chair: ' + fpEsc(name)]; if (chapEl && chapEl.value) bits.push('Chapter: ' + fpEsc(chapEl.value)); if (phoneEl && phoneEl.value) bits.push('Phone: ' + fpEsc(phoneEl.value)); if (emailEl && emailEl.value) bits.push('Email: ' + fpEsc(emailEl.value)); if (statusLabel) bits.push('Status: ' + fpEsc(statusLabel)); html += '
      ' + bits.join('  |  ') + '
      '; } if (coName) { var coBits = ['Co-Chair: ' + fpEsc(coName)]; if (coChapEl && coChapEl.value) coBits.push('Chapter: ' + fpEsc(coChapEl.value)); if (coPhoneEl && coPhoneEl.value) coBits.push('Phone: ' + fpEsc(coPhoneEl.value)); if (coEmailEl && coEmailEl.value) coBits.push('Email: ' + fpEsc(coEmailEl.value)); html += '
      ' + coBits.join('  |  ') + '
      '; } // Committee Members roster var members = (state.committeeMembers && state.committeeMembers[tabId]) ? state.committeeMembers[tabId].filter(function(m){ return m.name && m.name.trim(); }) : []; if (members.length) { html += '
      Committee Members:' + '' + '' + members.map(function(m){ return '' + '' + '' + '' + ''; }).join('') + '
      NameChapterRolePhoneEmail
      '+fpEsc(m.name)+''+fpEsc(m.chapter||'—')+''+fpEsc(m.role||'—')+''+fpEsc(m.phone||'—')+''+fpEsc(m.email||'—')+'
      '; } return html; } // Build the task table (used by both "chair" and "checklist" kinds, since both share // the same per-task key shape: tabId-si-ii with due-/asgn-/rh- elements rendered in DOM) function fpTaskTable(tabId) { var items = document.querySelectorAll('#sections-' + tabId + ' .check-item'); if (!items.length) return '

      No tasks added yet.

      '; var rows = Array.from(items).map(function(item, idx) { var cb = item.querySelector('input[type="checkbox"]'); var lbl = item.querySelector('.check-item-top label'); var taskText = lbl ? lbl.textContent.trim() : ''; var checked = cb ? cb.checked : false; var key = cb ? cb.id.replace('cb-','') : ''; var dueEl = document.getElementById('due-' + key); var asgnEl = document.getElementById('asgn-' + key); var due = dueEl && dueEl.value ? new Date(dueEl.value+'T00:00:00').toLocaleDateString('en-US',{month:'short',day:'numeric',year:'numeric'}) : ''; var asgn = asgnEl ? asgnEl.value.trim() : ''; // Remarks: most recent first, from state.remarksList[key] var remarksArr = (state.remarksList && state.remarksList[key]) ? state.remarksList[key] : []; var remarksHtml = remarksArr.length ? remarksArr.map(function(r){ return '
      [' + fpEsc(r.time) + '] ' + fpEsc(r.text) + '
      '; }).join('') : ''; var bg = idx % 2 === 0 ? '#fff' : '#f8faff'; var statusBadge = checked ? 'Done' : 'Open'; return '' + '' + (checked ? '✅' : '☐') + '' + '' + fpEsc(taskText) + '' + '' + fpEsc(asgn || '—') + '' + '' + (due || '—') + '' + '' + statusBadge + '' + '' + remarksHtml + '' + ''; }).join(''); return '' + '' + '' + '' + '' + '' + '' + '' + '' + rows + '
      TaskAssigned ToDue DateStatusRemarks
      '; } function fpNotesBlock(tabId) { var notesEl = document.getElementById('notes-' + tabId); if (!notesEl || !notesEl.value || !notesEl.value.trim()) return ''; return '
      ' + 'Section Notes:' + '

      ' + fpEsc(notesEl.value.trim()) + '

      '; } // Lightweight table renderers for data-table sections (budget, sponsors, vendors, etc.) function fpDataTableSummary(tabId, kind) { var notesHtml = fpNotesBlock(tabId); if (kind === 'budget') { var inc = state.budgetIncome || [], exp = state.budgetExpense || []; if (!inc.length && !exp.length) return '

      No budget entries yet.

      ' + notesHtml; var html = ''; if (inc.length) { html += '

      Income

      ' + inc.map(function(r){ return ''; }).join('') + '
      CategoryEst.Actual
      ' + fpEsc(r.cat||r.desc||'—') + '' + fpEsc(r.est||'—') + '' + fpEsc(r.actual||'—') + '
      '; } if (exp.length) { html += '

      Expenses

      ' + exp.map(function(r){ return ''; }).join('') + '
      CategoryEst.ActualStatus
      ' + fpEsc(r.cat||r.desc||'—') + '' + fpEsc(r.est||'—') + '' + fpEsc(r.actual||'—') + '' + fpEsc(r.status||'—') + '
      '; } return html + notesHtml; } if (kind === 'sponsors') { var arr = state.sponsors || []; if (!arr.length) return '

      No sponsors added yet.

      ' + notesHtml; return '' + arr.map(function(r){ return ''; }).join('') + '
      OrganizationLevelAmountStatus
      ' + fpEsc(r.org||'—') + '' + fpEsc(r.level||'—') + '' + fpEsc(r.amount||'—') + '' + fpEsc(r.status||'—') + '
      ' + notesHtml; } if (kind === 'exhibitors') { var arr = state.exhibitors || []; if (!arr.length) return '

      No exhibitors added yet.

      ' + notesHtml; return '' + arr.map(function(r){ return ''; }).join('') + '
      OrganizationBoothStatus
      ' + fpEsc(r.org||'—') + '' + fpEsc(r.booth||'—') + '' + fpEsc(r.status||'—') + '
      ' + notesHtml; } if (kind === 'vendors') { var arr = state.vendors || []; if (!arr.length) return '

      No vendors added yet.

      ' + notesHtml; return '' + arr.map(function(r){ return ''; }).join('') + '
      VendorCategoryStatus
      ' + fpEsc(r.name||'—') + '' + fpEsc(r.cat||'—') + '' + fpEsc(r.status||'—') + '
      ' + notesHtml; } if (kind === 'meetings') { var arr = state.meetings || []; if (!arr.length) return '

      No meetings scheduled yet.

      ' + notesHtml; return '' + arr.map(function(r){ return ''; }).join('') + '
      TitleDateTimeLocation
      ' + fpEsc(r.title||'—') + '' + fpEsc(r.date||'—') + '' + fpEsc(r.time||'—') + '' + fpEsc(r.location||'—') + '
      ' + notesHtml; } if (kind === 'dignitaries') { var arr = state.dignitaries || []; var greet = state.greetings || []; var html = ''; if (arr.length) { html += '' + arr.map(function(d){ return ''; }).join('') + '
      NameTitleSeatingEscort
      ' + fpEsc(d.name||'—') + '' + fpEsc(d.title||'—') + '' + fpEsc(d.seating||'—') + '' + fpEsc(d.escort||'—') + '
      '; } else html += '

      No dignitaries added yet.

      '; if (greet.length) { html += '

      Greetings & Dignitary Letters

      ' + greet.map(function(g){ return '
      ' + fpEsc(g.from||'TBD') + ' — ' + fpEsc(g.title||'') + ' (' + fpEsc(g.date||'TBD') + ')
      '; }).join(''); } // Also include the dignitaries tab's task checklist (Greetings & Dignitary Letters tasks) html += fpTaskTable('dignitaries'); return html + notesHtml; } if (kind === 'contracts') { var arr = state.contracts || []; if (!arr.length) return '

      No contracts added yet.

      ' + notesHtml; return '' + arr.map(function(c){ return ''; }).join('') + '
      TitleCategoryVendorStatus
      ' + fpEsc(c.title||'—') + '' + fpEsc(c.category||'—') + '' + fpEsc(c.vendor||'—') + '' + fpEsc(c.status||'—') + '
      ' + notesHtml; } if (kind === 'poc' || kind === 'planningpoc') { var arr = kind === 'poc' ? (state.pocs||[]) : (state.planningPocs||[]); if (!arr.length) return '

      No contacts added yet.

      ' + notesHtml; return '' + arr.map(function(p){ return ''; }).join('') + '
      NameTitlePhoneEmail
      ' + fpEsc(p.name||'—') + '' + fpEsc(p.title||'—') + '' + fpEsc(p.phone||'—') + '' + fpEsc(p.email||'—') + '
      ' + notesHtml; } if (kind === 'insurance') { var prov = (document.getElementById('ins-provider')||{}).value || ''; var pol = (document.getElementById('ins-policy')||{}).value || ''; var amt = (document.getElementById('ins-amount')||{}).value || ''; var html = ''; if (prov || pol || amt) { html += '
      ' + (prov?'Provider: '+fpEsc(prov)+'  |  ':'') + (pol?'Policy #: '+fpEsc(pol)+'  |  ':'') + (amt?'Coverage: '+fpEsc(amt):'') + '
      '; } html += fpTaskTable('insurance'); return html + notesHtml; } if (kind === 'tour') { var arr = state.tourStops || []; if (!arr.length) return '

      No tour stops recorded.

      ' + notesHtml; return '
        ' + arr.map(function(s){ return '
      1. ' + fpEsc(s.area||'Area') + '' + (s.notes?': '+fpEsc(s.notes):'') + '
      2. '; }).join('') + '
      ' + notesHtml; } if (kind === 'hotels') { var arr = state.hotels || []; if (!arr.length) return '

      No hotels added yet.

      ' + notesHtml; return '' + arr.map(function(h){ return ''; }).join('') + '
      HotelGroup Rate
      ' + fpEsc(h.name||'—') + '' + fpEsc(h.groupRate||'—') + '
      ' + notesHtml; } if (kind === 'notesonly') { return (notesHtml || '

      No notes added yet.

      '); } return notesHtml; } // MAIN BUILDER function buildFullPlan() { var c = document.getElementById('full-plan-content'); if (!c) return; var totalTasks = 0, doneTasks = 0; var tocSections = []; // Pre-scan for TOC + overall progress FULL_PLAN_STRUCTURE.forEach(function(cat) { cat[1].forEach(function(sec) { var tabId = sec[0]; var all = document.querySelectorAll('#sections-' + tabId + ' input[type="checkbox"]'); var done = document.querySelectorAll('#sections-' + tabId + ' input[type="checkbox"]:checked'); totalTasks += all.length; doneTasks += done.length; tocSections.push({ cat: cat[0], id: tabId, label: sec[1], count: all.length }); }); }); var overallPct = totalTasks ? Math.round(doneTasks / totalTasks * 100) : 0; // ── HEADER ── var html = '
      ' + '
      ' + '\u03a6\u0392\u03a3SIGMA1914' + '

      Phi Beta Sigma \u2013 State of Georgia Conference

      ' + '

      Full Combined Plan  \u00b7  ' + currentYear + ' Conference Year  \u00b7  Generated: ' + new Date().toLocaleString() + '

      ' + '
      ' + '
      ' + 'Overall Progress: ' + overallPct + '% complete  (' + doneTasks + ' / ' + totalTasks + ' tasks)' + '
      '; // ── TABLE OF CONTENTS ── html += '
      ' + '

      \ud83d\udcd1 Table of Contents

      ' + '
      '; var lastCat = null; tocSections.forEach(function(s) { var catHeader = ''; if (s.cat !== lastCat) { catHeader = '
      ' + fpEsc(s.cat) + '
      '; lastCat = s.cat; } html += catHeader + '' + '\u2022 ' + fpEsc(s.label) + '' + '' + s.count + (s.count===1?' task':' tasks') + '' + ''; }); html += '
      '; // ── EACH SECTION ── FULL_PLAN_STRUCTURE.forEach(function(cat) { var catName = cat[0]; html += '
      ' + '

      ' + fpEsc(catName) + '

      ' + '
      '; cat[1].forEach(function(sec) { var tabId = sec[0], label = sec[1], kind = sec[2]; var all = document.querySelectorAll('#sections-' + tabId + ' input[type="checkbox"]'); var done = document.querySelectorAll('#sections-' + tabId + ' input[type="checkbox"]:checked'); var pct = all.length ? Math.round(done.length / all.length * 100) : null; html += '
      ' + '

      ' + '' + fpEsc(label) + '' + (pct !== null ? '' + pct + '% (' + done.length + '/' + all.length + ')' : '') + '

      ' + '
      '; if (kind === 'chair' || kind === 'checklist') { html += fpChairHeader(tabId); html += fpTaskTable(tabId); html += fpNotesBlock(tabId); } else { html += fpDataTableSummary(tabId, kind); } html += '
      '; }); }); // ── OVERVIEW-LEVEL NOTES ── var ovNotes = (document.getElementById('notes-overview')||{}).value || ''; if (ovNotes.trim()) { html += '
      ' + '

      Overview-Level Notes

      ' + '

      ' + fpEsc(ovNotes.trim()) + '

      '; } // ── FOOTER ── html += '
      ' + 'PHI BETA SIGMA FRATERNITY, INC.  \u00b7  STATE OF GEORGIA CONFERENCE  \u00b7  CONFIDENTIAL PLANNING DOCUMENT' + '
      '; c.innerHTML = html; } // ── PRINT FULL PLAN (separate from view) ── function printFullPlan(isPDF) { buildFullPlan(); var content = document.getElementById('full-plan-content').innerHTML; var win = window.open('', '_blank'); if (!win) { showToast('Pop-up blocked. Please allow pop-ups for this site.', 'error'); return; } var pdfBanner = isPDF ? '
      ' + '\ud83d\udcbe Save as PDF: Press Ctrl+P (Windows) or Cmd+P (Mac) \u2192 ' + 'Change Destination to "Save as PDF" \u2192 Click Save' + '
      ' : ''; win.document.write('Phi Beta Sigma \u2013 GA Conference Full Combined Plan' + '' + pdfBanner + content + (isPDF ? '' : ''); win.document.close(); } function printCurrentTab(){ const active=document.querySelector('.tab-panel.active'); if(!active)return; document.querySelectorAll('.tab-panel').forEach(p=>p.classList.remove('print-active')); active.classList.add('print-active'); window.print(); } function printChapterEvent(n){ const card=document.getElementById(`chapter-event-${n}`); if(!card)return; const win=window.open('','_blank'); win.document.write(`Chapter Event ${n}

      Phi Beta Sigma – State of Georgia Conference

      Local Chapter Event ${n}

      ${card.querySelector('.chapter-event-body').innerHTML} `); win.document.close(); } // ═══════════ DOWNLOAD REPORT ═══════════ function downloadReport(){ refreshOverview(); const text=document.getElementById('master-log-output').textContent; const blob=new Blob([text],{type:'text/plain'}); const a=document.createElement('a');a.href=URL.createObjectURL(blob); a.download='PBS_GA_Conference_Master_Logistics_Report.txt';a.click(); } // ═══════════ SHARE ═══════════ function showShareModal(){document.getElementById('share-modal').style.display='flex';} function closeShareModal(){document.getElementById('share-modal').style.display='none';} function exportAndShare(){ const html=document.documentElement.outerHTML; const blob=new Blob([html],{type:'text/html'}); const a=document.createElement('a');a.href=URL.createObjectURL(blob); a.download='PBS_GA_Conference_Plan_SHARED.html';a.click(); closeShareModal(); } function copyShareInstructions(){ const txt=`To collaborate on the Phi Beta Sigma – State of Georgia Conference Plan:\n1. Save the shared HTML file to a shared cloud folder (Google Drive, SharePoint, Dropbox)\n2. Share the folder link with your team\n3. Each person opens the file in their browser, makes updates, then uses "Export & Share" to save and re-share the updated file\n4. For the most current version, always use the latest exported file from your shared folder`; navigator.clipboard.writeText(txt).then(()=>alert('Instructions copied to clipboard!')); } function saveAll(){ try { saveCurrentYear(); } catch(e) {} showToast('All data saved!'); var btn=document.querySelector('.btn-gold'); if(btn){var orig=btn.textContent;btn.textContent='✓ Saved!';btn.style.background='var(--success)';btn.style.color='#fff';setTimeout(function(){btn.textContent=orig;btn.style.background='';btn.style.color='';},2000);} } // ═══════════ TAB NAV ═══════════ function showTab(tabId){ // Permission check for regular users if(authDB.currentUser && authDB.currentUser.role==='user'){ const catMap={'overview':'Master Overview','fullplan':'Full Combined Plan','contracts':'Contract Repository', 'virtual':'Conference Virtual Session','friday':'Friday Operations Day','opening':'Opening Ceremony', 'pageant':'Pageant','stepshow':'Step Show','luncheon':'1914 Luncheon','workshop1':'Workshop 1','workshop2':'Workshop 2', 'workshop3':'Workshop 3','orchid':'Orchid Ball','necrology':'Necrology','chapters':'Local Chapters', 'greetings':'Greetings & Dignitary Letters','vendors':'Vendors','volunteers':'Volunteers', 'dignitaries':'Dignitary Management','poc':'Convention Center POCs','planningpoc':'Planning Committee POCs', 'insurance':'Insurance','tour':'Tour Convention Center','budget':'Budget','sponsors':'Sponsors', 'exhibitors':'Exhibitors','meetings':'Meetings'}; const cat=catMap[tabId]; if(cat&&!canAccessCategory(cat)){showToast('You do not have access to this section.','error');return;} } document.querySelectorAll('.tab-panel').forEach(p=>p.classList.remove('active')); document.querySelectorAll('nav.sidebar .nav-btn').forEach(b=>b.classList.remove('active')); const panel=document.getElementById('tab-'+tabId); if(panel)panel.classList.add('active'); const navBtn=document.getElementById('nav-'+tabId); if(navBtn)navBtn.classList.add('active'); if(tabId==='overview'){ refreshOverview(); } if(tabId==='fullplan'){ setTimeout(function(){try{buildFullPlan();}catch(e){}},100); } if(tabId==='printcenter'){ try{initPrintCenter();}catch(e){} } if(tabId==='dueout'){ setTimeout(function(){try{refreshDueOut();}catch(e){}},100); } if(tabId==='permissions'&&canManagePermissions()){ openAdminPanel('users'); } } // ═══════════ INIT ═══════════ function init(){ // Render all checklist tabs FIRST so DOM is ready try { Object.keys(TABS_DATA).forEach(function(tid){ try { if(TABS_DATA[tid] && TABS_DATA[tid].sections) renderSections(tid, TABS_DATA[tid].sections); } catch(e){ console.error('renderSections failed for '+tid+':', e); } }); } catch(e){ console.error('TABS_DATA loop error:', e); } try { renderInsuranceChecklist(); } catch(e){ console.error('renderInsuranceChecklist:', e); } // Initialize tables try { addPocRow(); addPocRow(); } catch(e){} try { addVendorRow(); addVendorRow(); } catch(e){} // Tour stops try { if(state.tourStops.length === 0){ [{area:'Main Ballroom',notes:''},{area:'Registration Lobby',notes:''},{area:'Workshop Room A',notes:''},{area:'Workshop Room B',notes:''},{area:'Workshop Room C',notes:''},{area:'VIP / Green Room',notes:''},{area:'Loading Dock / Vendor Access',notes:''},{area:'Parking Areas',notes:''}] .forEach(function(s){ state.tourStops.push(s); }); } renderTour(); } catch(e){ console.error('renderTour:', e); } // Render all other sections try { renderBudget(); } catch(e){} try { renderSponsors(); } catch(e){} try { renderExhibitors(); } catch(e){} try { renderMeetings(); } catch(e){} try { renderCalendar(); } catch(e){} try { renderContracts(); } catch(e){} try { renderPlanningPocs(); } catch(e){} try { renderGreetings(); } catch(e){} try { renderDignitaries(); } catch(e){} try { renderVolunteers(); } catch(e){} try { renderResources(); } catch(e){} try { renderAgendas(); } catch(e){} try { renderHotels(); } catch(e){} // Attachment lists try { ['overview','virtual','friday','opening','pageant','stepshow','luncheon','workshop1','workshop2','workshop3','orchid','necrology','greetings','vendors','volunteers','dignitaries','insurance','tour','poc','planningpoc','chapters','ce1','ce2','ce3','budget','sponsors','exhibitors','meetings','contracts','resources','agenda','hotels'].forEach(function(id){ try { renderAttachments(id); } catch(e){} }); } catch(e){} // Auth - shows login screen (runs AFTER DOM is populated) try { initAuth(); } catch(e){ console.error('initAuth error:', e); } // Start multi-year save/load system (also loads saved data) try { initAutoSave(); } catch(e){ console.warn('initAutoSave:', e); } } window.addEventListener('DOMContentLoaded', function(){ try { init(); attachCurrencyFormatting(); // Restore any custom tabs created by user setTimeout(function(){ try { restoreCustomTabs(); } catch(e) {} }, 500); // Render remarks for static (chair) tab HTML once saved data has loaded setTimeout(function(){ try { renderAllRemarksLists(); } catch(e) {} }, 700); setTimeout(function(){ try { renderAllCommitteeMembers(); } catch(e) {} }, 750); // Import Jun 24 2026 Pending Actions tasks on every load (idempotent - skips if already present) setTimeout(function(){ try { seedPendingActionsV2(); restoreChairAddedTasks(); restoreDueDatesAndAssigned(); renderAllRemarksLists(); } catch(e) { console.warn('seed error',e); } }, 850); } catch(e){ console.error('Init failed:', e); // Even if init fails, make sure auth overlay is visible var overlay = document.getElementById('auth-overlay'); if(overlay) overlay.style.display = 'flex'; } try { attachAutoSaveListeners(); } catch(e){} }); // ═══════════════════════════════════════════════════════════ // ADMIN PANEL – FULL LOGIC // ═══════════════════════════════════════════════════════════ let adminPanelCurrentSection = 'users'; let activityLog = []; function loadActivityLog(){ try{ activityLog = JSON.parse(localStorage.getItem('pbs_activity_log')||'[]'); } catch(e){ activityLog=[]; } } function saveActivityLog(){ try{ localStorage.setItem('pbs_activity_log', JSON.stringify(activityLog.slice(0,100))); }catch(e){} } function logActivity(action, detail=''){ const actor = authDB.currentUser ? authDB.currentUser.name||authDB.currentUser.email : 'System'; activityLog.unshift({ time: new Date().toLocaleString(), actor, action, detail }); saveActivityLog(); } // ── DROPDOWN ── function toggleAdminDropdown(){ const menu = document.getElementById('admin-dropdown-menu'); const btn = document.getElementById('admin-btn'); const isOpen = menu.classList.contains('open'); menu.classList.toggle('open', !isOpen); btn.classList.toggle('open', !isOpen); if(!isOpen){ document.addEventListener('click', closeAdminDropdownOutside, {once:true}); } } function closeAdminDropdownOutside(e){ const wrap = document.getElementById('admin-dropdown-wrap'); if(wrap && !wrap.contains(e.target)){ document.getElementById('admin-dropdown-menu').classList.remove('open'); document.getElementById('admin-btn').classList.remove('open'); } } // ── OPEN / CLOSE PANEL ── function openAdminPanel(section='users'){ document.getElementById('admin-dropdown-menu').classList.remove('open'); document.getElementById('admin-btn').classList.remove('open'); loadActivityLog(); const overlay = document.getElementById('admin-panel-overlay'); overlay.classList.add('open'); switchAPSection(section); apRefreshAll(); } function closeAdminPanel(){ document.getElementById('admin-panel-overlay').classList.remove('open'); } // ESC key closes admin panel document.addEventListener('keydown',e=>{ if(e.key==='Escape'){ closeAdminPanel(); document.getElementById('admin-dropdown-menu').classList.remove('open'); document.getElementById('admin-btn') && document.getElementById('admin-btn').classList.remove('open'); } }); function handleAdminOverlayClick(e){ if(e.target === document.getElementById('admin-panel-overlay')) closeAdminPanel(); } function switchAPSection(section){ adminPanelCurrentSection = section; document.querySelectorAll('.ap-section').forEach(s=>s.classList.remove('active')); document.querySelectorAll('.ap-nav-item').forEach(n=>n.classList.remove('active')); const sec = document.getElementById('ap-section-'+section); if(sec) sec.classList.add('active'); const nav = document.querySelector(`.ap-nav-item[data-section="${section}"]`); if(nav) nav.classList.add('active'); // Render relevant section if(section==='users') apRenderUserList(); if(section==='permissions') apRenderPermSection(); if(section==='categories') apRenderCategoryMatrix(); if(section==='tasks') apRenderTaskSection(); if(section==='overview') apRenderOverview(); if(section==='master') apRenderMasterSection(); } function apRefreshAll(){ apRenderUserList(); apRenderPermSection(); // Update role badge const u = authDB.currentUser; const badge = document.getElementById('ap-role-badge'); if(badge && u){ badge.textContent = u.role==='master'?'⭐ Master Admin':u.role==='admin'?'🔑 Admin':'👤 User'; badge.className = 'role-badge '+(u.role==='master'?'role-master':u.role==='admin'?'role-admin':'role-user'); } } // ── USER LIST RENDER ── function apRenderUserList(){ const list = document.getElementById('ap-user-list'); const countEl = document.getElementById('ap-user-count'); if(!list) return; loadAuthDB(); countEl && (countEl.textContent = authDB.users.length); const curRole = authDB.currentUser?.role; list.innerHTML = ''; authDB.users.forEach((u,i)=>{ const isSelf = u.email === authDB.currentUser?.email; const isMaster = u.role==='master'; const canEdit = curRole==='master' || (curRole==='admin' && !isMaster); const avatarColor = u.role==='master'?'#092d72':u.role==='admin'?'#1a52c4':'#5a7abf'; const tagClass = u.role==='master'?'ap-tag-master':u.role==='admin'?'ap-tag-admin':'ap-tag-user'; const roleLabel = u.role==='master'?'⭐ Master Admin':u.role==='admin'?'🔑 Admin':'👤 User'; const row = document.createElement('div'); row.className = 'ap-user-row'; row.innerHTML = `
      ${(u.name||u.email).charAt(0).toUpperCase()}
      ${roleLabel} ${u.active?'Active':'Inactive'} ${canEdit && !isSelf ? ` ` : ''}`; list.appendChild(row); }); } function apAddUser(){ if(!canManagePermissions()){showToast('Access denied.','error');return;} const name = document.getElementById('new-user-name').value.trim(); const email = document.getElementById('new-user-email').value.trim().toLowerCase(); const role = document.getElementById('new-user-role').value; const active= document.getElementById('new-user-status').value === 'true'; const allAcc= document.getElementById('new-user-all-access').checked; if(!email){ showToast('Email is required.','error'); return; } if(authDB.users.find(u=>u.email.toLowerCase()===email)){ showToast('User already exists.','error'); return; } if(role==='master' && authDB.currentUser?.role!=='master'){ showToast('Only Master Admin can create Master Admin accounts.','error'); return; } const newUser = { id:'user_'+Date.now(), name:name||email.split('@')[0], email, role, categories: allAcc||role==='master'||role==='admin' ? [...ALL_CATEGORIES] : [], permissions: allAcc||role==='master'||role==='admin' ? [...ALL_PERMISSIONS] : [], includeAll: allAcc||role==='master'||role==='admin', active, addedDate: new Date().toLocaleDateString(), lastActive:'—', googlePhoto:'' }; authDB.users.push(newUser); saveAuthDB(); logActivity('User Created', `${newUser.name} (${email}) as ${role}`); apRenderUserList(); document.getElementById('new-user-name').value=''; document.getElementById('new-user-email').value=''; showToast(`User ${newUser.name} added as ${role}.`); } function apChangeRole(userId, newRole){ if(!canManagePermissions()){showToast('Access denied.','error');return;} const u = authDB.users.find(u=>u.id===userId); if(!u) return; if(u.role==='master'&&authDB.currentUser?.role!=='master'){showToast('Cannot change Master Admin role.','error');return;} const oldRole = u.role; u.role = newRole; if(newRole==='master'||newRole==='admin'){ u.includeAll=true; u.categories=[...ALL_CATEGORIES]; u.permissions=[...ALL_PERMISSIONS]; } saveAuthDB(); logActivity('Role Changed', `${u.name} (${u.email}) from ${oldRole} → ${newRole}`); apRenderUserList(); showToast(`${u.name} role updated to ${newRole}.`); } function apToggleActive(userId){ const u = authDB.users.find(u=>u.id===userId); if(!u||u.role==='master'){showToast('Cannot deactivate Master Admin.','error');return;} u.active = !u.active; saveAuthDB(); logActivity(u.active?'User Activated':'User Deactivated', `${u.name} (${u.email})`); apRenderUserList(); showToast(`${u.name} ${u.active?'activated':'deactivated'}.`); } function apDeleteUser(userId){ const u = authDB.users.find(u=>u.id===userId); if(!u){return;} if(u.role==='master'){showToast('Cannot delete Master Admin.','error');return;} if(!confirm(`Permanently delete ${u.name} (${u.email})? This cannot be undone.`)) return; authDB.users = authDB.users.filter(u=>u.id!==userId); saveAuthDB(); logActivity('User Deleted', `${u.name} (${u.email})`); apRenderUserList(); showToast(`${u.name} deleted.`); } // ── PERMISSIONS SECTION ── function apRenderPermSection(){ const sel = document.getElementById('perm-user-select'); if(!sel) return; loadAuthDB(); const curVal = sel.value; sel.innerHTML = ''; authDB.users.forEach(u=>{ const opt = document.createElement('option'); opt.value = u.id; opt.textContent = `${u.name||u.email} (${u.role})`; if(u.id===curVal) opt.selected=true; sel.appendChild(opt); }); if(curVal) loadUserPermissions(); } function loadUserPermissions(){ const sel = document.getElementById('perm-user-select'); const area = document.getElementById('perm-edit-area'); const grid = document.getElementById('perm-list-grid'); const inclAll = document.getElementById('perm-include-all'); if(!sel||!area||!grid) return; const userId = sel.value; if(!userId){ area.style.display='none'; return; } const u = authDB.users.find(u=>u.id===userId); if(!u){ area.style.display='none'; return; } area.style.display='block'; inclAll.checked = !!u.includeAll; grid.style.opacity = u.includeAll?'0.4':'1'; grid.style.pointerEvents = u.includeAll?'none':'auto'; grid.innerHTML = ALL_PERMISSIONS.map(p=>`
      `).join(''); } function apToggleIncludeAll(){ const inclAll = document.getElementById('perm-include-all'); const grid = document.getElementById('perm-list-grid'); const isAll = inclAll.checked; grid.style.opacity = isAll?'0.4':'1'; grid.style.pointerEvents = isAll?'none':'auto'; if(isAll){ grid.querySelectorAll('input[type="checkbox"]').forEach(cb=>cb.checked=true); } } function apSavePermissions(){ const sel = document.getElementById('perm-user-select'); const inclAll = document.getElementById('perm-include-all'); if(!sel.value){showToast('Select a user first.','error');return;} const u = authDB.users.find(u=>u.id===sel.value); if(!u) return; u.includeAll = inclAll.checked; if(u.includeAll){ u.permissions=[...ALL_PERMISSIONS]; u.categories=[...ALL_CATEGORIES]; } else { u.permissions = ALL_PERMISSIONS.filter(p=>{ const cb = document.getElementById('pp-'+p.replace(/\W/g,'_')); return cb && cb.checked; }); } saveAuthDB(); logActivity('Permissions Updated', `${u.name} (${u.email}) – includeAll:${u.includeAll}`); showToast(`Permissions saved for ${u.name}.`); } function apRevokeAllPermissions(){ const sel = document.getElementById('perm-user-select'); if(!sel.value){showToast('Select a user first.','error');return;} const u = authDB.users.find(u=>u.id===sel.value); if(!u||u.role==='master'){showToast('Cannot revoke Master Admin permissions.','error');return;} if(!confirm(`Revoke ALL permissions for ${u.name}?`)) return; u.permissions=[]; u.categories=[]; u.includeAll=false; saveAuthDB(); logActivity('All Permissions Revoked', `${u.name} (${u.email})`); loadUserPermissions(); showToast(`All permissions revoked for ${u.name}.`,'error'); } // ── CATEGORY MATRIX ── function apRenderCategoryMatrix(){ const wrap = document.getElementById('cat-matrix-wrap'); if(!wrap) return; loadAuthDB(); const users = authDB.users; const cats = ALL_CATEGORIES.slice(0,15); // show first 15 to keep table manageable let html = `
      `; cats.forEach(c=>{ html+=``; }); html+=``; users.forEach((u,ui)=>{ const isMaster = u.role==='master'; html+=``; html+=``; cats.forEach(cat=>{ const has = u.includeAll||(u.categories||[]).includes(cat); html+=``; }); html+=``; }); html+=`
      UserRole${c}All
      ${esc(u.name||u.email)}
      ${esc(u.email)}
      ${u.role}
      `; html+=`

      Showing ${cats.length} of ${ALL_CATEGORIES.length} categories. Changes auto-save when checkbox is clicked.

      `; wrap.innerHTML = html; } function apToggleCatAccess(userId,cat,checked){ const u = authDB.users.find(u=>u.id===userId); if(!u||u.role==='master') return; if(!u.categories) u.categories=[]; if(checked){ if(!u.categories.includes(cat)) u.categories.push(cat); } else { u.categories = u.categories.filter(c=>c!==cat); u.includeAll=false; } saveAuthDB(); logActivity('Category Access Changed', `${u.name}: ${cat} → ${checked?'granted':'revoked'}`); } function apToggleUserAllCats(userId,checked){ const u = authDB.users.find(u=>u.id===userId); if(!u||u.role==='master') return; u.includeAll = checked; u.categories = checked ? [...ALL_CATEGORIES] : []; if(checked) u.permissions = [...ALL_PERMISSIONS]; saveAuthDB(); logActivity('Include All Toggled', `${u.name}: ${checked?'granted all':'revoked all'}`); apRenderCategoryMatrix(); } // ── TASK SECTION ── function apRenderTaskSection(){ // Fill task override select with regular users const sel = document.getElementById('task-override-select'); const overrideList = document.getElementById('task-override-list'); if(!sel) return; loadAuthDB(); sel.innerHTML=''; const users = authDB.users.filter(u=>u.role==='user'); users.forEach(u=>{ const opt=document.createElement('option'); opt.value=u.id; opt.textContent=`${u.name||u.email} (${u.email})`; sel.appendChild(opt); }); // Show users with override const overrideUsers = authDB.users.filter(u=>u.role==='user'&&u.taskOverride); if(overrideList){ overrideList.innerHTML = overrideUsers.length?overrideUsers.map(u=>`
      Task Override
      `).join('') :'

      No users with task override granted.

      '; } // Task stats const statsEl = document.getElementById('task-stats-content'); if(statsEl){ const total = Object.keys(state.checkboxes).length; const done = Object.values(state.checkboxes).filter(Boolean).length; const pct = total?Math.round(done/total*100):0; statsEl.innerHTML=`
      ${total}
      Total Tasks
      ${done}
      Completed
      ${total-done}
      Remaining
      ${pct}%
      Overall Progress
      `; } } function apGrantTaskOverride(){ const sel = document.getElementById('task-override-select'); if(!sel.value){showToast('Select a user.','error');return;} const u = authDB.users.find(u=>u.id===sel.value); if(!u) return; u.taskOverride=true; saveAuthDB(); logActivity('Task Override Granted', `${u.name} (${u.email})`); apRenderTaskSection(); showToast(`Task completion granted to ${u.name}.`); } function apRevokeTaskOverride(userId){ const u=authDB.users.find(u=>u.id===userId); if(!u) return; u.taskOverride=false; saveAuthDB(); logActivity('Task Override Revoked', `${u.name} (${u.email})`); apRenderTaskSection(); showToast(`Task completion revoked from ${u.name}.`); } // ── OVERVIEW ── function apRenderOverview(){ loadAuthDB(); loadActivityLog(); const grid = document.getElementById('ap-stat-grid'); const table = document.getElementById('ap-overview-table'); const log = document.getElementById('activity-log-content'); if(!grid) return; const total = authDB.users.length; const masters = authDB.users.filter(u=>u.role==='master').length; const admins = authDB.users.filter(u=>u.role==='admin').length; const users = authDB.users.filter(u=>u.role==='user').length; const active = authDB.users.filter(u=>u.active).length; grid.innerHTML=`
      ${total}
      Total Users
      ${masters}
      Master Admins
      ${admins}
      Admins
      ${users}
      Regular Users
      ${active}
      Active Accounts
      `; if(table){ table.innerHTML=`${authDB.users.map(u=>` `).join('')}
      NameEmailRoleStatusCategoriesAddedLast Active
      ${esc(u.name||'—')} ${esc(u.email)} ${u.role==='master'?'⭐ Master':u.role==='admin'?'🔑 Admin':'👤 User'} ${u.active?'Active':'Inactive'} ${u.includeAll?'All':((u.categories||[]).length+' cats')} ${u.addedDate||'—'} ${u.lastActive||'—'}
      `; } if(log){ if(!activityLog.length){ log.innerHTML='
      No activity recorded yet.
      '; } else { log.innerHTML=activityLog.map(e=>`
      ${e.time} 📌 ${esc(e.actor)} — ${esc(e.action)}${e.detail?': '+esc(e.detail)+'':''}
      `).join(''); } } } // ── MASTER CONTROLS ── function apRenderMasterSection(){ loadAuthDB(); const promSel = document.getElementById('promote-user-sel'); const demotSel = document.getElementById('demote-admin-sel'); if(promSel){ promSel.innerHTML=''; authDB.users.filter(u=>u.role==='user').forEach(u=>{ const o=document.createElement('option');o.value=u.id;o.textContent=`${u.name||u.email} (${u.email})`;promSel.appendChild(o); }); } if(demotSel){ demotSel.innerHTML=''; authDB.users.filter(u=>u.role==='admin').forEach(u=>{ const o=document.createElement('option');o.value=u.id;o.textContent=`${u.name||u.email} (${u.email})`;demotSel.appendChild(o); }); } } function apPromoteToAdmin(){ const sel=document.getElementById('promote-user-sel'); if(!sel.value){showToast('Select a user.','error');return;} const u=authDB.users.find(u=>u.id===sel.value); if(!u)return; u.role='admin'; u.includeAll=true; u.categories=[...ALL_CATEGORIES]; u.permissions=[...ALL_PERMISSIONS]; saveAuthDB(); logActivity('Promoted to Admin',`${u.name} (${u.email})`); apRenderMasterSection(); apRenderUserList(); showToast(`${u.name} promoted to Admin.`); } function apDemoteAdmin(){ const sel=document.getElementById('demote-admin-sel'); if(!sel.value){showToast('Select an admin.','error');return;} const u=authDB.users.find(u=>u.id===sel.value); if(!u)return; if(!confirm(`Remove Admin privileges from ${u.name}? They will become a regular User.`))return; u.role='user'; u.includeAll=false; u.categories=[]; u.permissions=[]; saveAuthDB(); logActivity('Admin Demoted',`${u.name} (${u.email})`); apRenderMasterSection(); apRenderUserList(); showToast(`${u.name} demoted to User.`,'error'); } function apForceSignOutAll(){ if(!confirm('Force sign-out all users? They will need to sign in again.'))return; sessionStorage.clear(); logActivity('Force Sign-Out All','Initiated by '+authDB.currentUser?.name); showToast('All sessions cleared. Users will need to sign in again.'); } function apClearActivityLog(){ if(!confirm('Clear the entire activity log? This cannot be undone.'))return; activityLog=[];saveActivityLog(); apRenderOverview(); showToast('Activity log cleared.'); } function apExportUserDB(){ const data=JSON.stringify({exportDate:new Date().toISOString(),users:authDB.users.map(u=>({...u,googlePhoto:'[redacted]'}))},null,2); const blob=new Blob([data],{type:'application/json'}); const a=document.createElement('a');a.href=URL.createObjectURL(blob); a.download='PBS_GA_Users_'+new Date().toLocaleDateString().replace(/\//g,'-')+'.json'; a.click(); logActivity('User DB Exported',''); showToast('User database exported.'); }