@@ -14,8 +14,16 @@ pub fn admin_dashboard_html(
1414 response_counts : & HashMap < String , i64 > ,
1515 base_url : & str ,
1616) -> String {
17- // Forms section
18- let form_rows: String = forms
17+ // Split forms into active and archived
18+ let active_forms: Vec < _ > = forms. iter ( ) . filter ( |f| !f. archived ) . collect ( ) ;
19+ let archived_forms: Vec < _ > = forms. iter ( ) . filter ( |f| f. archived ) . collect ( ) ;
20+
21+ // Split calendars into active and archived
22+ let active_calendars: Vec < _ > = calendars. iter ( ) . filter ( |c| !c. archived ) . collect ( ) ;
23+ let archived_calendars: Vec < _ > = calendars. iter ( ) . filter ( |c| c. archived ) . collect ( ) ;
24+
25+ // Active forms section
26+ let form_rows: String = active_forms
1927 . iter ( )
2028 . map ( |f| {
2129 let count = response_counts. get ( & f. slug ) . unwrap_or ( & 0 ) ;
@@ -28,6 +36,11 @@ pub fn admin_dashboard_html(
2836 <button onclick=\" copyLink('/f/{slug}', this)\" class=\" btn btn-sm\" >Copy Link</button>
2937 <a href=\" {base_url}/admin/forms/{slug}/responses\" class=\" btn btn-sm\" >Responses</a>
3038 <a href=\" {base_url}/admin/forms/{slug}\" class=\" btn btn-sm\" >Edit</a>
39+ <button class=\" btn btn-sm btn-secondary\"
40+ hx-post=\" {base_url}/admin/forms/{slug}/archive\"
41+ hx-confirm=\" Archive this form? It will become read-only.\"
42+ hx-target=\" closest tr\"
43+ hx-swap=\" outerHTML\" >Archive</button>
3144 </td>
3245 </tr>" ,
3346 base_url = base_url,
@@ -38,8 +51,35 @@ pub fn admin_dashboard_html(
3851 } )
3952 . collect ( ) ;
4053
41- // Calendars section
42- let calendar_rows: String = calendars
54+ // Archived forms section
55+ let archived_form_rows: String = archived_forms
56+ . iter ( )
57+ . map ( |f| {
58+ let count = response_counts. get ( & f. slug ) . unwrap_or ( & 0 ) ;
59+ format ! (
60+ "<tr>
61+ <td>{name} <span style=\" color:#666;font-size:0.85em;\" >(archived)</span></td>
62+ <td><code>/f/{slug}</code></td>
63+ <td>{count}</td>
64+ <td>
65+ <a href=\" {base_url}/admin/forms/{slug}/responses\" class=\" btn btn-sm\" >Responses</a>
66+ <a href=\" {base_url}/admin/forms/{slug}\" class=\" btn btn-sm\" >View</a>
67+ <button class=\" btn btn-sm\"
68+ hx-post=\" {base_url}/admin/forms/{slug}/unarchive\"
69+ hx-target=\" closest tr\"
70+ hx-swap=\" outerHTML\" >Unarchive</button>
71+ </td>
72+ </tr>" ,
73+ base_url = base_url,
74+ name = html_escape( & f. name) ,
75+ slug = html_escape( & f. slug) ,
76+ count = count
77+ )
78+ } )
79+ . collect ( ) ;
80+
81+ // Active calendars section
82+ let calendar_rows: String = active_calendars
4383 . iter ( )
4484 . map ( |cal| {
4585 format ! (
@@ -50,11 +90,11 @@ pub fn admin_dashboard_html(
5090 <td>{updated}</td>
5191 <td>
5292 <a href=\" {base_url}/admin/calendars/{id}\" class=\" btn btn-sm\" >Edit</a>
53- <button class=\" btn btn-sm btn-danger \"
54- hx-delete =\" {base_url}/admin/calendars/{id}\"
55- hx-confirm=\" Delete this calendar?\"
93+ <button class=\" btn btn-sm btn-secondary \"
94+ hx-post =\" {base_url}/admin/calendars/{id}/archive \"
95+ hx-confirm=\" Archive this calendar? It will become read-only. \"
5696 hx-target=\" closest tr\"
57- hx-swap=\" outerHTML\" >Delete </button>
97+ hx-swap=\" outerHTML\" >Archive </button>
5898 </td>
5999 </tr>" ,
60100 base_url = base_url,
@@ -67,6 +107,82 @@ pub fn admin_dashboard_html(
67107 } )
68108 . collect ( ) ;
69109
110+ // Archived calendars section
111+ let archived_calendar_rows: String = archived_calendars
112+ . iter ( )
113+ . map ( |cal| {
114+ format ! (
115+ "<tr>
116+ <td>{name} <span style=\" color:#666;font-size:0.85em;\" >(archived)</span></td>
117+ <td>{booking_count} booking links</td>
118+ <td>{view_count} view links</td>
119+ <td>{updated}</td>
120+ <td>
121+ <a href=\" {base_url}/admin/calendars/{id}\" class=\" btn btn-sm\" >View</a>
122+ <button class=\" btn btn-sm\"
123+ hx-post=\" {base_url}/admin/calendars/{id}/unarchive\"
124+ hx-target=\" closest tr\"
125+ hx-swap=\" outerHTML\" >Unarchive</button>
126+ </td>
127+ </tr>" ,
128+ base_url = base_url,
129+ id = html_escape( & cal. id) ,
130+ name = html_escape( & cal. name) ,
131+ booking_count = cal. booking_links. len( ) ,
132+ view_count = cal. view_links. len( ) ,
133+ updated = html_escape( & cal. updated_at. split( 'T' ) . next( ) . unwrap_or( "" ) ) ,
134+ )
135+ } )
136+ . collect ( ) ;
137+
138+ // Build archived sections HTML
139+ let archived_forms_section = if archived_forms. is_empty ( ) {
140+ String :: new ( )
141+ } else {
142+ format ! (
143+ "<details style=\" margin-top: 1rem;\" >
144+ <summary style=\" cursor: pointer; color: #666;\" >Archived Forms ({count})</summary>
145+ <table style=\" margin-top: 0.5rem;\" >
146+ <thead>
147+ <tr>
148+ <th>Name</th>
149+ <th>URL</th>
150+ <th>Responses</th>
151+ <th>Actions</th>
152+ </tr>
153+ </thead>
154+ <tbody>{rows}</tbody>
155+ </table>
156+ </details>" ,
157+ count = archived_forms. len( ) ,
158+ rows = archived_form_rows
159+ )
160+ } ;
161+
162+ let archived_calendars_section = if archived_calendars. is_empty ( ) {
163+ String :: new ( )
164+ } else {
165+ format ! (
166+ "<details style=\" margin-top: 1rem;\" >
167+ <summary style=\" cursor: pointer; color: #666;\" >Archived Calendars ({count})</summary>
168+ <table style=\" margin-top: 0.5rem;\" >
169+ <thead>
170+ <tr>
171+ <th>Name</th>
172+ <th>Booking Links</th>
173+ <th>View Links</th>
174+ <th>Updated</th>
175+ <th>Actions</th>
176+ </tr>
177+ </thead>
178+ <tbody>{rows}</tbody>
179+ </table>
180+ </details>" ,
181+ count = archived_calendars. len( ) ,
182+ rows = archived_calendar_rows
183+ )
184+ } ;
185+
70186 let content = format ! (
71187 "<h1 style=\" display: flex; align-items: center; gap: 0.5rem;\" >
72188 <img src=\" /logo.svg\" alt=\" \" style=\" width: 32px; height: 32px;\" >
@@ -90,6 +206,7 @@ pub fn admin_dashboard_html(
90206 {form_rows}
91207 </tbody>
92208 </table>
209+ {archived_forms_section}
93210
94211 <h2 style=\" margin-top: 2rem;\" >Calendars</h2>
95212 <p style=\" margin: 1rem 0;\" >
@@ -111,6 +228,7 @@ pub fn admin_dashboard_html(
111228 {calendar_rows}
112229 </tbody>
113230 </table>
231+ {archived_calendars_section}
114232 <script>
115233 function copyLink(path, btn) {{
116234 const url = window.location.origin + path;
@@ -136,6 +254,8 @@ pub fn admin_dashboard_html(
136254 } else {
137255 calendar_rows
138256 } ,
257+ archived_forms_section = archived_forms_section,
258+ archived_calendars_section = archived_calendars_section,
139259 hash = HASH ,
140260 ) ;
141261
@@ -273,6 +393,7 @@ pub fn admin_calendar_html(calendar: &CalendarConfig, base_url: &str) -> String
273393
274394 <p><a href=\" {base_url}/admin\" >← Back to Dashboard</a></p>
275395 <h1>{name}</h1>
396+ {archived_notice}
276397
277398 <div class=\" tabs\" >
278399 <button class=\" tab active\" onclick=\" showTab('settings')\" >Settings</button>
@@ -327,12 +448,21 @@ pub fn admin_calendar_html(calendar: &CalendarConfig, base_url: &str) -> String
327448 <h2>Feed Links (iCal)</h2>
328449 <p style=\" margin-bottom: 1rem; color: #666;\" >Subscribe to this calendar from other apps.</p>
329450 <button class=\" btn\" hx-post=\" {base_url}/admin/calendars/{id}/feed\"
330- hx-target=\" {hash}feed-links tbody\" hx-swap=\" beforeend\" >+ Add Feed Link</button>
451+ hx-target=\" {hash}feed-links tbody\" hx-swap=\" beforeend\" {readonly_disabled} >+ Add Feed Link</button>
331452 <table id=\" feed-links\" style=\" margin-top: 1rem;\" >
332453 <thead><tr><th>Name</th><th>URL</th><th>Status</th><th>Actions</th></tr></thead>
333454 <tbody>{feed_links_html}</tbody>
334455 </table>
335456 </div>
457+
458+ <div class=\" card\" style=\" border-color: #dc3545;\" >
459+ <h2 style=\" color: #dc3545;\" >Danger Zone</h2>
460+ <p style=\" margin-bottom: 1rem; color: #666;\" >Permanently delete this calendar and all its data.</p>
461+ <button class=\" btn btn-danger\"
462+ hx-delete=\" {base_url}/admin/calendars/{id}\"
463+ hx-confirm=\" Are you sure you want to permanently delete this calendar? This action cannot be undone.\"
464+ hx-on::after-request=\" if(event.detail.successful) window.location.href='/admin'\" >Delete Calendar</button>
465+ </div>
336466 </div>
337467
338468 <div id=\" tab-events\" class=\" tab-content\" >
@@ -395,10 +525,21 @@ pub fn admin_calendar_html(calendar: &CalendarConfig, base_url: &str) -> String
395525 view_links_html = view_links_html,
396526 feed_links_html = feed_links_html,
397527 instagram_sources_html = instagram_sources_html,
528+ archived_notice = if calendar. archived {
529+ "<div class=\" card\" style=\" background: #fff3cd; border-color: #ffc107; margin-bottom: 1rem;\" >
530+ <p style=\" margin: 0; color: #856404;\" ><strong>This calendar is archived.</strong> It is read-only. Unarchive from the dashboard to make changes.</p>
531+ </div>"
532+ } else { "" } ,
533+ readonly_disabled = if calendar. archived { " disabled" } else { "" } ,
398534 hash = HASH ,
399535 ) ;
400536
401- base_html ( & format ! ( "Edit: {}" , calendar. name) , & content, & calendar. style )
537+ let title = if calendar. archived {
538+ format ! ( "{} (Archived)" , calendar. name)
539+ } else {
540+ format ! ( "Edit: {}" , calendar. name)
541+ } ;
542+ base_html ( & title, & content, & calendar. style )
402543}
403544
404545pub fn admin_events_html ( calendar : & CalendarConfig , events : & [ CalendarEvent ] , base_url : & str ) -> String {
0 commit comments