Remove production checklist and email/calendar/contacts app; v1.1.37
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+5
-454
@@ -693,34 +693,12 @@ interface Subscriber {
|
||||
source: 'contact-form' | 'download'
|
||||
}
|
||||
|
||||
type ChecklistPhase = 'pre' | 'post'
|
||||
|
||||
interface PodcastChecklistTask {
|
||||
id: string
|
||||
label: string
|
||||
phase: ChecklistPhase
|
||||
}
|
||||
|
||||
interface PodcastChecklistEpisode {
|
||||
id: string
|
||||
series: string
|
||||
episodeNumber: number | null
|
||||
title: string
|
||||
datePublished: string
|
||||
expanded: boolean
|
||||
tasks: Record<string, boolean>
|
||||
}
|
||||
|
||||
interface PodcastChecklistData {
|
||||
tasks: PodcastChecklistTask[]
|
||||
episodes: PodcastChecklistEpisode[]
|
||||
}
|
||||
|
||||
type StringField = Exclude<keyof SiteContent, 'customLinks' | 'customBlocks' | 'redirects' | 'podcastFeaturedLinks' | 'finishedBooks' | 'testimonials' | 'seo' | 'legal' | 'whereToNextCards' | 'colossiansStudySections' | 'studies'>
|
||||
|
||||
type AdminView =
|
||||
| 'dashboard' | 'homepage' | 'start-here' | 'about' | 'contact'
|
||||
| 'podcast' | 'current-series' | 'episode-highlights' | 'podcast-checklist'
|
||||
| 'podcast' | 'current-series' | 'episode-highlights'
|
||||
| 'downloads' | 'custom-links' | 'content-blocks'
|
||||
| 'questions' | 'study-comments' | 'analytics' | 'assets' | 'colossians-study' | 'qr-codes'
|
||||
| 'subscribers' | 'study-users' | 'email-templates'
|
||||
@@ -837,7 +815,7 @@ const ADMIN_SECTION_LINKS: Partial<Record<AdminView, AdminSectionLink[]>> = {
|
||||
],
|
||||
}
|
||||
|
||||
type PodcastTab = 'current-series' | 'episode-highlights' | 'finished-books' | 'podcast-checklist' | 'episode-scripts' | 'rss-feed'
|
||||
type PodcastTab = 'current-series' | 'episode-highlights' | 'finished-books' | 'episode-scripts' | 'rss-feed'
|
||||
|
||||
type MainContentSection = 'hero' | 'start-here' | 'about' | 'contact' | 'series' | 'share' | 'prism' | 'global' | 'email-templates'
|
||||
|
||||
@@ -1203,10 +1181,7 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
|
||||
const [rssEpisodes, setRssEpisodes] = useState<Array<{ title: string; audioUrl: string; duration: string; episode: string }>>([])
|
||||
|
||||
const [podcastChecklist, setPodcastChecklist] = useState<PodcastChecklistData>({ tasks: [], episodes: [] })
|
||||
const [podcastChecklistStatus, setPodcastChecklistStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle')
|
||||
const [podcastChecklistMsg, setPodcastChecklistMsg] = useState('')
|
||||
const [dashboardNow, setDashboardNow] = useState(() => new Date())
|
||||
const [dashboardNow, setDashboardNow] = useState(() => new Date())
|
||||
const [manualQuestion, setManualQuestion] = useState({
|
||||
firstName: '',
|
||||
email: '',
|
||||
@@ -1423,16 +1398,6 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
.then(r => (r.ok ? r.json() : Promise.reject()))
|
||||
.then(data => setDownloadStats((data as { counts: Record<string, number> }).counts ?? {}))
|
||||
.catch(() => {})
|
||||
|
||||
fetch('/api/admin-podcast-checklist')
|
||||
.then(r => (r.ok ? r.json() : Promise.reject(new Error('Failed to load podcast checklist'))))
|
||||
.then(data => {
|
||||
const checklist = (data as { checklist?: PodcastChecklistData }).checklist
|
||||
if (checklist?.tasks && checklist?.episodes) {
|
||||
setPodcastChecklist(checklist)
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -1741,150 +1706,7 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
setForm(f => ({ ...f, podcastFeaturedLinks: (f.podcastFeaturedLinks ?? []).filter(link => link.id !== id) }))
|
||||
}
|
||||
|
||||
function addChecklistTask(phase: ChecklistPhase) {
|
||||
const id = `task-${Date.now().toString(36)}`
|
||||
setPodcastChecklist(prev => ({
|
||||
tasks: [...prev.tasks, { id, label: '', phase }],
|
||||
episodes: prev.episodes.map(episode => ({
|
||||
...episode,
|
||||
tasks: {
|
||||
...episode.tasks,
|
||||
[id]: false,
|
||||
},
|
||||
})),
|
||||
}))
|
||||
}
|
||||
|
||||
function updateChecklistTask(id: string, field: 'label' | 'phase', value: string) {
|
||||
setPodcastChecklist(prev => ({
|
||||
...prev,
|
||||
tasks: prev.tasks.map(task => task.id === id
|
||||
? {
|
||||
...task,
|
||||
[field]: field === 'phase' ? (value === 'post' ? 'post' : 'pre') : value,
|
||||
}
|
||||
: task),
|
||||
}))
|
||||
}
|
||||
|
||||
function removeChecklistTask(id: string) {
|
||||
setPodcastChecklist(prev => ({
|
||||
tasks: prev.tasks.filter(task => task.id !== id),
|
||||
episodes: prev.episodes.map(episode => {
|
||||
const nextTasks = { ...episode.tasks }
|
||||
delete nextTasks[id]
|
||||
return {
|
||||
...episode,
|
||||
tasks: nextTasks,
|
||||
}
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
function addChecklistEpisode() {
|
||||
const id = `episode-${Date.now().toString(36)}`
|
||||
setPodcastChecklist(prev => ({
|
||||
...prev,
|
||||
episodes: [
|
||||
...prev.episodes,
|
||||
{
|
||||
id,
|
||||
series: 'Colossians',
|
||||
episodeNumber: null,
|
||||
title: '',
|
||||
datePublished: '',
|
||||
expanded: false,
|
||||
tasks: Object.fromEntries(prev.tasks.map(task => [task.id, false])),
|
||||
},
|
||||
],
|
||||
}))
|
||||
}
|
||||
|
||||
function updateChecklistEpisode(id: string, field: 'series' | 'episodeNumber' | 'title' | 'datePublished', value: string) {
|
||||
setPodcastChecklist(prev => ({
|
||||
...prev,
|
||||
episodes: prev.episodes.map(episode => {
|
||||
if (episode.id !== id) return episode
|
||||
if (field === 'episodeNumber') {
|
||||
const parsed = Number.parseInt(value, 10)
|
||||
return {
|
||||
...episode,
|
||||
episodeNumber: Number.isNaN(parsed) ? null : parsed,
|
||||
}
|
||||
}
|
||||
return {
|
||||
...episode,
|
||||
[field]: value,
|
||||
}
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
function toggleChecklistEpisodeTask(episodeId: string, taskId: string) {
|
||||
setPodcastChecklist(prev => ({
|
||||
...prev,
|
||||
episodes: prev.episodes.map(episode => {
|
||||
if (episode.id !== episodeId) return episode
|
||||
return {
|
||||
...episode,
|
||||
tasks: {
|
||||
...episode.tasks,
|
||||
[taskId]: !episode.tasks[taskId],
|
||||
},
|
||||
}
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
function resetChecklistEpisode(episodeId: string) {
|
||||
setPodcastChecklist(prev => ({
|
||||
...prev,
|
||||
episodes: prev.episodes.map(episode => {
|
||||
if (episode.id !== episodeId) return episode
|
||||
return {
|
||||
...episode,
|
||||
datePublished: '',
|
||||
tasks: Object.fromEntries(prev.tasks.map(task => [task.id, false])),
|
||||
}
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
function removeChecklistEpisode(id: string) {
|
||||
setPodcastChecklist(prev => ({
|
||||
...prev,
|
||||
episodes: prev.episodes.filter(episode => episode.id !== id),
|
||||
}))
|
||||
}
|
||||
|
||||
async function handleSavePodcastChecklist() {
|
||||
setPodcastChecklistStatus('saving')
|
||||
setPodcastChecklistMsg('')
|
||||
try {
|
||||
const res = await fetch('/api/admin-podcast-checklist', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ checklist: podcastChecklist }),
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({})) as { message?: string }
|
||||
throw new Error(data.message ?? 'Failed to save checklist')
|
||||
}
|
||||
|
||||
const data = await res.json() as { checklist?: PodcastChecklistData }
|
||||
if (data.checklist?.tasks && data.checklist?.episodes) {
|
||||
setPodcastChecklist(data.checklist)
|
||||
}
|
||||
setPodcastChecklistStatus('saved')
|
||||
setTimeout(() => setPodcastChecklistStatus('idle'), 3000)
|
||||
} catch (err) {
|
||||
setPodcastChecklistMsg(err instanceof Error ? err.message : 'Failed to save checklist')
|
||||
setPodcastChecklistStatus('error')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAssetUpload(event: ChangeEvent<HTMLInputElement>) {
|
||||
async function handleAssetUpload(event: ChangeEvent<HTMLInputElement>) {
|
||||
const file = event.target.files?.[0]
|
||||
if (!file) return
|
||||
|
||||
@@ -2773,67 +2595,6 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
questionPage * QUESTION_PAGE_SIZE,
|
||||
(questionPage + 1) * QUESTION_PAGE_SIZE,
|
||||
)
|
||||
const checklistTaskOrder = [
|
||||
'verify_script',
|
||||
'read_script',
|
||||
'record',
|
||||
'mix',
|
||||
'edit',
|
||||
'video_script',
|
||||
'post_spotify',
|
||||
'update_website',
|
||||
'send_email',
|
||||
]
|
||||
const checklistTaskOrderIndex = new Map(checklistTaskOrder.map((id, index) => [id, index]))
|
||||
const checklistTasksSorted = [...podcastChecklist.tasks].sort((a, b) => {
|
||||
const aIndex = checklistTaskOrderIndex.get(a.id)
|
||||
const bIndex = checklistTaskOrderIndex.get(b.id)
|
||||
const aKnown = typeof aIndex === 'number'
|
||||
const bKnown = typeof bIndex === 'number'
|
||||
|
||||
if (aKnown && bKnown) return aIndex - bIndex
|
||||
if (aKnown && !bKnown) return -1
|
||||
if (!aKnown && bKnown) return 1
|
||||
return a.label.localeCompare(b.label, undefined, { sensitivity: 'base' })
|
||||
})
|
||||
const checklistPreTasks = checklistTasksSorted.filter(task => task.phase === 'pre')
|
||||
const checklistPostTasks = checklistTasksSorted.filter(task => task.phase === 'post')
|
||||
const checklistEpisodesSorted = [...podcastChecklist.episodes].sort((a, b) => {
|
||||
const isDraft = (episode: PodcastChecklistEpisode) => {
|
||||
const hasNumber = episode.episodeNumber !== null
|
||||
const hasTitle = Boolean(episode.title?.trim())
|
||||
const hasDate = Boolean(episode.datePublished?.trim())
|
||||
return !hasNumber && !hasTitle && !hasDate
|
||||
}
|
||||
|
||||
const aDraft = isDraft(a)
|
||||
const bDraft = isDraft(b)
|
||||
if (aDraft && !bDraft) return -1
|
||||
if (!aDraft && bDraft) return 1
|
||||
|
||||
const seriesOrder = (series: string) => {
|
||||
const key = series.trim().toLowerCase()
|
||||
if (key === 'titus') return 0
|
||||
if (key === 'colossians') return 1
|
||||
return 2
|
||||
}
|
||||
|
||||
const bySeries = seriesOrder(a.series) - seriesOrder(b.series)
|
||||
if (bySeries !== 0) return bySeries
|
||||
|
||||
const nameSort = a.series.localeCompare(b.series, undefined, { sensitivity: 'base' })
|
||||
if (nameSort !== 0) return nameSort
|
||||
|
||||
const aNum = a.episodeNumber
|
||||
const bNum = b.episodeNumber
|
||||
if (aNum === null && bNum === null) return a.title.localeCompare(b.title, undefined, { sensitivity: 'base' })
|
||||
if (aNum === null) return 1
|
||||
if (bNum === null) return -1
|
||||
if (aNum !== bNum) return aNum - bNum
|
||||
|
||||
return a.title.localeCompare(b.title, undefined, { sensitivity: 'base' })
|
||||
})
|
||||
|
||||
function renderSaveStatus() {
|
||||
return (
|
||||
<>
|
||||
@@ -2843,15 +2604,6 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
)
|
||||
}
|
||||
|
||||
function renderPodcastChecklistStatus() {
|
||||
return (
|
||||
<>
|
||||
{podcastChecklistStatus === 'saved' && <p className="admin-status admin-status--ok">✓ Checklist saved.</p>}
|
||||
{podcastChecklistStatus === 'error' && <p className="admin-status admin-status--err">✗ {podcastChecklistMsg}</p>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="admin-shell">
|
||||
{/* ── Top bar ── */}
|
||||
@@ -3535,8 +3287,7 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
<button type="button" className={`admin-tab${podcastTab === 'current-series' ? ' admin-tab--active' : ''}`} onClick={() => setPodcastTab('current-series')}>Current Series</button>
|
||||
<button type="button" className={`admin-tab${podcastTab === 'episode-highlights' ? ' admin-tab--active' : ''}`} onClick={() => setPodcastTab('episode-highlights')}>Ep. Highlights</button>
|
||||
<button type="button" className={`admin-tab${podcastTab === 'finished-books' ? ' admin-tab--active' : ''}`} onClick={() => setPodcastTab('finished-books')}>Finished Books</button>
|
||||
<button type="button" className={`admin-tab${podcastTab === 'podcast-checklist' ? ' admin-tab--active' : ''}`} onClick={() => setPodcastTab('podcast-checklist')}>Production Checklist</button>
|
||||
<button type="button" className={`admin-tab${podcastTab === 'episode-scripts' ? ' admin-tab--active' : ''}`} onClick={() => setPodcastTab('episode-scripts')}>Episode Scripts</button>
|
||||
<button type="button" className={`admin-tab${podcastTab === 'episode-scripts' ? ' admin-tab--active' : ''}`} onClick={() => setPodcastTab('episode-scripts')}>Episode Scripts</button>
|
||||
<button type="button" className={`admin-tab${podcastTab === 'rss-feed' ? ' admin-tab--active' : ''}`} onClick={() => setPodcastTab('rss-feed')}>RSS Feed</button>
|
||||
</div>
|
||||
</section>
|
||||
@@ -3738,206 +3489,6 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* PODCAST CHECKLIST */}
|
||||
{(adminView === 'podcast-checklist' || (adminView === 'podcast' && podcastTab === 'podcast-checklist')) && (
|
||||
<section className="admin-panel-section" aria-label="Podcast production checklist">
|
||||
<div className="admin-panel-head">
|
||||
<h2>Podcast Production Checklist</h2>
|
||||
<p>Track production progress for each episode. Add as many tasks and episodes as you need, then save.</p>
|
||||
</div>
|
||||
|
||||
<div className="admin-content-summary">
|
||||
<article className="admin-summary-card">
|
||||
<h3>Total Episodes</h3>
|
||||
<p>{podcastChecklist.episodes.length}</p>
|
||||
</article>
|
||||
<article className="admin-summary-card">
|
||||
<h3>Pre-Publish Tasks</h3>
|
||||
<p>{checklistPreTasks.length}</p>
|
||||
</article>
|
||||
<article className="admin-summary-card">
|
||||
<h3>Post-Publish Tasks</h3>
|
||||
<p>{checklistPostTasks.length}</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<AdminCollapsibleCard
|
||||
title="Task Template"
|
||||
subtitle={`${podcastChecklist.tasks.length} tasks configured`}
|
||||
className="admin-collapsible-card--group"
|
||||
>
|
||||
<div className="admin-archive-subsection-actions" style={{ marginBottom: '0.75rem' }}>
|
||||
<button type="button" className="btn-admin-add" onClick={() => addChecklistTask('pre')}>+ Add Pre-Publish Task</button>
|
||||
<button type="button" className="btn-admin-add" onClick={() => addChecklistTask('post')}>+ Add Post-Publish Task</button>
|
||||
</div>
|
||||
|
||||
{podcastChecklist.tasks.length === 0 && (
|
||||
<p className="admin-stats-note">No tasks yet. Add a pre-publish or post-publish task above.</p>
|
||||
)}
|
||||
|
||||
{checklistTasksSorted.map(task => (
|
||||
<div key={task.id} className="admin-array-row admin-array-row--nested">
|
||||
<div className="admin-array-fields" style={{ display: 'grid', gap: '0.75rem', gridTemplateColumns: '1fr minmax(11rem, 15rem)' }}>
|
||||
<div className="admin-field">
|
||||
<label htmlFor={`checklist-task-label-${task.id}`}>Task Name</label>
|
||||
<input
|
||||
id={`checklist-task-label-${task.id}`}
|
||||
type="text"
|
||||
value={task.label}
|
||||
onChange={e => updateChecklistTask(task.id, 'label', e.target.value)}
|
||||
placeholder="e.g. Upload transcript"
|
||||
/>
|
||||
</div>
|
||||
<div className="admin-field">
|
||||
<label htmlFor={`checklist-task-phase-${task.id}`}>Phase</label>
|
||||
<select
|
||||
id={`checklist-task-phase-${task.id}`}
|
||||
value={task.phase}
|
||||
onChange={e => updateChecklistTask(task.id, 'phase', e.target.value)}
|
||||
>
|
||||
<option value="pre">Pre-Publish</option>
|
||||
<option value="post">Post-Publish</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" className="btn-admin-remove" onClick={() => removeChecklistTask(task.id)}>Remove</button>
|
||||
</div>
|
||||
))}
|
||||
</AdminCollapsibleCard>
|
||||
|
||||
<div className="admin-archive-subsection">
|
||||
<div className="admin-archive-subsection-head">
|
||||
<h5>Episodes</h5>
|
||||
<div className="admin-archive-subsection-actions">
|
||||
<button type="button" className="btn-admin-add" onClick={addChecklistEpisode}>+ Add Episode</button>
|
||||
<button type="button" className="btn-admin-save" onClick={handleSavePodcastChecklist} disabled={podcastChecklistStatus === 'saving'}>
|
||||
{podcastChecklistStatus === 'saving' ? 'Saving Checklist…' : 'Save Checklist'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{podcastChecklist.episodes.length === 0 && (
|
||||
<p className="admin-stats-note">No episodes yet. Add one above to start tracking progress.</p>
|
||||
)}
|
||||
|
||||
{checklistEpisodesSorted.map(episode => {
|
||||
const doneCount = checklistTasksSorted.reduce((count, task) => count + (episode.tasks[task.id] ? 1 : 0), 0)
|
||||
const totalCount = checklistTasksSorted.length
|
||||
const nextTask = checklistTasksSorted.find(task => !episode.tasks[task.id])
|
||||
const episodeLabelParts = [episode.series?.trim()]
|
||||
if (episode.episodeNumber !== null) {
|
||||
episodeLabelParts.push(String(episode.episodeNumber))
|
||||
}
|
||||
if (episode.title?.trim()) {
|
||||
episodeLabelParts.push(episode.title.trim())
|
||||
}
|
||||
const episodeLabel = episodeLabelParts.filter(Boolean).join(' - ') || 'Untitled'
|
||||
|
||||
return (
|
||||
<AdminCollapsibleCard
|
||||
key={episode.id}
|
||||
title={episodeLabel}
|
||||
subtitle={totalCount > 0
|
||||
? `${doneCount}/${totalCount} tasks completed • Next: ${nextTask?.label ?? 'All done'}`
|
||||
: 'No tasks assigned yet'}
|
||||
>
|
||||
<div className="admin-array-row admin-array-row--nested">
|
||||
<div className="admin-array-fields" style={{ display: 'grid', gap: '0.75rem', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))' }}>
|
||||
<div className="admin-field">
|
||||
<label htmlFor={`checklist-series-${episode.id}`}>Series</label>
|
||||
<input
|
||||
id={`checklist-series-${episode.id}`}
|
||||
type="text"
|
||||
value={episode.series}
|
||||
onChange={e => updateChecklistEpisode(episode.id, 'series', e.target.value)}
|
||||
placeholder="Colossians"
|
||||
/>
|
||||
</div>
|
||||
<div className="admin-field">
|
||||
<label htmlFor={`checklist-episode-number-${episode.id}`}>Episode Number</label>
|
||||
<input
|
||||
id={`checklist-episode-number-${episode.id}`}
|
||||
type="number"
|
||||
value={episode.episodeNumber ?? ''}
|
||||
onChange={e => updateChecklistEpisode(episode.id, 'episodeNumber', e.target.value)}
|
||||
min={0}
|
||||
/>
|
||||
</div>
|
||||
<div className="admin-field">
|
||||
<label htmlFor={`checklist-title-${episode.id}`}>Title (optional)</label>
|
||||
<input
|
||||
id={`checklist-title-${episode.id}`}
|
||||
type="text"
|
||||
value={episode.title}
|
||||
onChange={e => updateChecklistEpisode(episode.id, 'title', e.target.value)}
|
||||
placeholder="Grace that Trains Us"
|
||||
/>
|
||||
</div>
|
||||
<div className="admin-field">
|
||||
<label htmlFor={`checklist-date-${episode.id}`}>Date Published</label>
|
||||
<input
|
||||
id={`checklist-date-${episode.id}`}
|
||||
type="date"
|
||||
value={episode.datePublished}
|
||||
onChange={e => updateChecklistEpisode(episode.id, 'datePublished', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{checklistPreTasks.length > 0 && (
|
||||
<div className="admin-archive-subsection">
|
||||
<div className="admin-archive-subsection-head">
|
||||
<h5>Pre-Publish Tasks</h5>
|
||||
</div>
|
||||
<div className="admin-array-fields">
|
||||
{checklistPreTasks.map(task => (
|
||||
<label key={task.id} style={{ display: 'flex', alignItems: 'center', gap: '0.6rem' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={episode.tasks[task.id] === true}
|
||||
onChange={() => toggleChecklistEpisodeTask(episode.id, task.id)}
|
||||
/>
|
||||
<span>{task.label || 'Untitled task'}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{checklistPostTasks.length > 0 && (
|
||||
<div className="admin-archive-subsection">
|
||||
<div className="admin-archive-subsection-head">
|
||||
<h5>Post-Publish Tasks</h5>
|
||||
</div>
|
||||
<div className="admin-array-fields">
|
||||
{checklistPostTasks.map(task => (
|
||||
<label key={task.id} style={{ display: 'flex', alignItems: 'center', gap: '0.6rem' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={episode.tasks[task.id] === true}
|
||||
onChange={() => toggleChecklistEpisodeTask(episode.id, task.id)}
|
||||
/>
|
||||
<span>{task.label || 'Untitled task'}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', gap: '0.6rem', marginTop: '0.8rem' }}>
|
||||
<button type="button" className="btn-admin-remove" onClick={() => resetChecklistEpisode(episode.id)}>Reset Progress</button>
|
||||
<button type="button" className="btn-admin-remove" onClick={() => removeChecklistEpisode(episode.id)}>Remove Episode</button>
|
||||
</div>
|
||||
</AdminCollapsibleCard>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{renderPodcastChecklistStatus()}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* DOWNLOADS */}
|
||||
{adminView === 'downloads' && (
|
||||
<section className="admin-panel-section">
|
||||
|
||||
Reference in New Issue
Block a user