import { useBorgesStore } from '../../store/borgesStore' import { PromptHero } from './PromptHero' import { WritingStats } from './WritingStats' function daysSince(iso: string): number { return Math.floor((Date.now() - new Date(iso).getTime()) / 86_400_000) } export function Dashboard(): JSX.Element { const { stories, submissions, markets, setActiveStory, markSaved, isDirty, activeStoryPath, activeStoryContent } = useBorgesStore() const openStory = async (path: string, id: string): Promise => { if (isDirty && activeStoryPath) { await window.api.writeStory(activeStoryPath, activeStoryContent) await window.api.saveRevision(activeStoryPath, activeStoryContent) markSaved() } const content = await window.api.readStory(path) setActiveStory(path, id, content) } // Stories ready to submit: no active pending, never submitted or last status rejected const readyToSubmit = stories.filter((story) => { const storySubs = submissions.filter((s) => s.storyId === story.id) const hasActive = storySubs.some((s) => s.status === 'pending' || s.status === 'pending-revision') if (hasActive) return false if (storySubs.length === 0) return true const last = storySubs.sort((a, b) => b.submittedAt.localeCompare(a.submittedAt))[0] return last.status === 'rejected' || last.status === 'withdrawn' }) // Pending submissions const pending = submissions .filter((s) => s.status === 'pending' || s.status === 'pending-revision') .sort((a, b) => a.submittedAt.localeCompare(b.submittedAt)) // Recent activity (last 10 non-pending changes) const recent = submissions .filter((s) => s.status === 'accepted' || s.status === 'rejected') .sort((a, b) => b.statusUpdatedAt.localeCompare(a.statusUpdatedAt)) .slice(0, 10) const totalWords = stories.reduce((s, story) => s + story.wordCount, 0) return (
{stories.length === 0 ? 'Welcome to Borges. Create your first story to get started.' : `${stories.length} ${stories.length === 1 ? 'story' : 'stories'} · ${totalWords.toLocaleString()} words total`}
{/* Ready to submit */}
Ready to submit ({readyToSubmit.length})
{readyToSubmit.length === 0 &&
All stories are out or in progress.
} {readyToSubmit.slice(0, 8).map((story) => (
openStory(story.path, story.id)}> {story.meta.title || story.id} {story.wordCount.toLocaleString()}w
))}
{/* Pending submissions */}
Out ({pending.length})
{pending.length === 0 &&
Nothing currently submitted.
} {pending.map((sub) => { const story = stories.find((s) => s.id === sub.storyId) const market = markets.find((m) => m.id === sub.marketId) const days = daysSince(sub.submittedAt) const isOverdue = market?.responseTimeWeeks && days > market.responseTimeWeeks * 7 return (
story && openStory(story.path, story.id)}> {story?.meta.title || sub.storyId} → {market?.name ?? sub.marketId} {days}d{isOverdue ? ' ⚠' : ''}
) })}
{/* Recent activity */}
Recent activity
{recent.length === 0 &&
No acceptances or rejections yet.
} {recent.map((sub) => { const story = stories.find((s) => s.id === sub.storyId) const market = markets.find((m) => m.id === sub.marketId) return (
story && openStory(story.path, story.id)}> {story?.meta.title || sub.storyId} {sub.status === 'accepted' ? '✓' : '✗'} {market?.name ?? sub.marketId}
) })}
{/* Writing stats */} {/* Word count overview */}
Word counts
{stories.length === 0 &&
No stories yet.
} {[...stories].sort((a, b) => b.wordCount - a.wordCount).slice(0, 10).map((story) => { const target = story.meta.wordCountTarget return (
openStory(story.path, story.id)}> {story.meta.title || story.id} {story.wordCount.toLocaleString()}{target ? `/${target.toLocaleString()}` : ''}
) })}
) }