slideshare/src/components/presentations/PresentationsList.tsx
Michael Mainguy 1008bd4bca Implement slide preview and fix import standards project-wide
## New Feature: Full Screen Slide Preview
- Add SlidePreviewModal component for full screen slide preview in SlideEditor
- ESC key support and temporary hint for user guidance
- Proper aspect ratio handling with theme CSS inheritance
- Modal follows existing UI patterns for consistency

## Import Standards Compliance (31 files updated)
- Fix all imports to use explicit .tsx/.ts extensions per IMPORT_STANDARDS.md
- Eliminate barrel imports in App.tsx for better Vite tree shaking
- Add direct imports with explicit paths across entire codebase
- Preserve CSS imports and external library imports unchanged

## Code Architecture Improvements
- Add comprehensive CSS & Component Architecture Guidelines to CLAUDE.md
- Document modal patterns, aspect ratio handling, and CSS reuse principles
- Reference all coding standards files for consistent development workflow
- Prevent future CSS overcomplication and component duplication

## Performance Optimizations
- Enable Vite tree shaking with proper import structure
- Improve module resolution speed with explicit extensions
- Optimize build performance through direct imports

## Files Changed
- 31 TypeScript/React files with import fixes
- 2 new SlidePreviewModal files (component + CSS)
- Updated project documentation and coding guidelines
- Fixed aspect ratio CSS patterns across components

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-21 06:52:56 -05:00

257 lines
9.2 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import type { Presentation } from '../../types/presentation.ts';
import { getAllPresentations, deletePresentation } from '../../utils/presentationStorage.ts';
import { loggers } from '../../utils/logger.ts';
import './PresentationsList.css';
export const PresentationsList: React.FC = () => {
const navigate = useNavigate();
const [presentations, setPresentations] = useState<Presentation[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [deleting, setDeleting] = useState<string | null>(null);
useEffect(() => {
loadPresentations();
}, []);
const loadPresentations = async () => {
try {
setLoading(true);
setError(null);
const allPresentations = await getAllPresentations();
setPresentations(allPresentations);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load presentations');
} finally {
setLoading(false);
}
};
const handleDeletePresentation = async (id: string, name: string) => {
if (!confirm(`Are you sure you want to delete "${name}"? This action cannot be undone.`)) {
return;
}
try {
setDeleting(id);
await deletePresentation(id);
setPresentations(prev => prev.filter(p => p.metadata.id !== id));
} catch (err) {
loggers.presentation.error('Failed to delete presentation', err instanceof Error ? err : new Error(String(err)));
alert('Failed to delete presentation. Please try again.');
} finally {
setDeleting(null);
}
};
const handleEditPresentation = (id: string, slideCount: number) => {
const slideNumber = slideCount > 0 ? 1 : 1; // Always go to slide 1, or empty state
navigate(`/presentations/${id}/edit/slides/${slideNumber}`);
};
const handleViewPresentation = (id: string, slideCount: number) => {
const slideNumber = slideCount > 0 ? 1 : 1; // Always go to slide 1, or empty state
navigate(`/presentations/${id}/view/slides/${slideNumber}`);
};
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
};
if (loading) {
return (
<div className="presentations-list">
<div className="loading-content">
<div className="loading-spinner">Loading presentations...</div>
</div>
</div>
);
}
if (error) {
return (
<div className="presentations-list">
<div className="error-content">
<h2>Error Loading Presentations</h2>
<p>{error}</p>
<button
onClick={loadPresentations}
className="action-button secondary"
>
Try Again
</button>
</div>
</div>
);
}
return (
<div className="presentations-list">
<header className="list-header">
<div className="header-content">
<h1>My Presentations</h1>
<p>Manage and organize your presentation library</p>
</div>
<div className="header-actions">
<Link
to="/presentations/new"
className="action-button primary"
>
Create New Presentation
</Link>
</div>
</header>
<main className="list-content">
{presentations.length === 0 ? (
<div className="empty-state">
<div className="empty-content">
<h2>No presentations yet</h2>
<p>Create your first presentation to get started</p>
<Link
to="/presentations/new"
className="action-button primary large"
>
Create Your First Presentation
</Link>
</div>
</div>
) : (
<div className="presentations-grid">
{presentations.map((presentation) => (
<div key={presentation.metadata.id} className="presentation-card">
<div className="card-header">
<div className="presentation-info">
<h3 className="presentation-name">{presentation.metadata.name}</h3>
{presentation.metadata.description && (
<p className="presentation-description">
{presentation.metadata.description}
</p>
)}
</div>
<div className="presentation-actions">
<button
type="button"
className="action-icon"
onClick={() => handleEditPresentation(presentation.metadata.id, presentation.slides.length)}
title="Edit presentation"
>
</button>
<button
type="button"
className="action-icon"
onClick={() => handleViewPresentation(presentation.metadata.id, presentation.slides.length)}
title="View presentation"
>
👁
</button>
<button
type="button"
className="action-icon delete"
onClick={() => handleDeletePresentation(presentation.metadata.id, presentation.metadata.name)}
disabled={deleting === presentation.metadata.id}
title="Delete presentation"
>
{deleting === presentation.metadata.id ? '⏳' : '🗑️'}
</button>
</div>
</div>
<div className="card-content">
<div className="presentation-stats">
<div className="stat-item">
<span className="stat-label">Theme</span>
<span className="stat-value theme-name">{presentation.metadata.theme}</span>
</div>
<div className="stat-item">
<span className="stat-label">Slides</span>
<span className="stat-value slides-count">
{presentation.slides.length} slide{presentation.slides.length !== 1 ? 's' : ''}
</span>
</div>
</div>
<div className="presentation-meta">
<div className="meta-item">
<span className="meta-label">Created</span>
<span className="meta-value">{formatDate(presentation.metadata.createdAt)}</span>
</div>
{presentation.metadata.updatedAt !== presentation.metadata.createdAt && (
<div className="meta-item">
<span className="meta-label">Updated</span>
<span className="meta-value">{formatDate(presentation.metadata.updatedAt)}</span>
</div>
)}
</div>
</div>
<div className="card-footer">
<button
type="button"
className="card-action primary"
onClick={() => handleEditPresentation(presentation.metadata.id, presentation.slides.length)}
>
Edit
</button>
<button
type="button"
className="card-action secondary"
onClick={() => handleViewPresentation(presentation.metadata.id, presentation.slides.length)}
>
View
</button>
{presentation.slides.length > 0 && (
<button
type="button"
className="card-action secondary"
onClick={() => {
// TODO: Implement presentation mode
alert('Presentation mode coming soon!');
}}
>
Present
</button>
)}
</div>
</div>
))}
</div>
)}
{presentations.length > 0 && (
<div className="list-footer">
<div className="summary-stats">
<p>
{presentations.length} presentation{presentations.length !== 1 ? 's' : ''} {' '}
{presentations.reduce((total, p) => total + p.slides.length, 0)} total slides
</p>
</div>
<div className="footer-actions">
<button
onClick={loadPresentations}
className="action-button secondary"
>
Refresh
</button>
<Link
to="/presentations/new"
className="action-button primary"
>
Create New
</Link>
</div>
</div>
)}
</main>
</div>
);
};