reaminig 5-t-9
This commit is contained in:
@@ -1,13 +1,17 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { Loader2, Play } from 'lucide-react';
|
||||
import { builderApi } from '../../api/builder.api';
|
||||
import type { SiteBlueprint } from '../../types/siteBuilder';
|
||||
import { Card } from '../../components/common/Card';
|
||||
import { useBuilderStore } from '../../state/builderStore';
|
||||
import { ProgressModal } from '../../components/common/ProgressModal';
|
||||
|
||||
export function SiteDashboard() {
|
||||
const [blueprints, setBlueprints] = useState<SiteBlueprint[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | undefined>();
|
||||
const { generateAllPages, isGenerating, generationProgress } = useBuilderStore();
|
||||
const [showProgress, setShowProgress] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
@@ -24,32 +28,73 @@ export function SiteDashboard() {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const handleGenerateAll = async (blueprintId: number) => {
|
||||
setShowProgress(true);
|
||||
try {
|
||||
await generateAllPages(blueprintId);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to generate pages');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card title="Blueprint history" description="Every generated structure is stored and can be reopened.">
|
||||
{loading && (
|
||||
<div className="sb-loading">
|
||||
<Loader2 className="spin" size={18} /> Loading blueprints…
|
||||
</div>
|
||||
)}
|
||||
<>
|
||||
<Card title="Blueprint history" description="Every generated structure is stored and can be reopened.">
|
||||
{loading && (
|
||||
<div className="sb-loading">
|
||||
<Loader2 className="spin" size={18} /> Loading blueprints…
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <p className="sb-error">{error}</p>}
|
||||
{error && <p className="sb-error">{error}</p>}
|
||||
|
||||
{!loading && !blueprints.length && (
|
||||
<p className="sb-muted">You haven’t generated any sites yet. Launch the wizard to create your first one.</p>
|
||||
)}
|
||||
{!loading && !blueprints.length && (
|
||||
<p className="sb-muted">You haven't generated any sites yet. Launch the wizard to create your first one.</p>
|
||||
)}
|
||||
|
||||
<ul className="sb-blueprint-list">
|
||||
{blueprints.map((bp) => (
|
||||
<li key={bp.id}>
|
||||
<div>
|
||||
<strong>{bp.name}</strong>
|
||||
<span>{bp.description}</span>
|
||||
</div>
|
||||
<span className={`status-dot status-${bp.status}`}>{bp.status}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Card>
|
||||
<ul className="sb-blueprint-list">
|
||||
{blueprints.map((bp) => (
|
||||
<li key={bp.id}>
|
||||
<div>
|
||||
<strong>{bp.name}</strong>
|
||||
<span>{bp.description}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
{bp.status === 'ready' && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleGenerateAll(bp.id)}
|
||||
disabled={isGenerating}
|
||||
className="sb-button sb-button--primary"
|
||||
style={{ display: 'flex', alignItems: 'center', gap: '4px', padding: '4px 12px' }}
|
||||
>
|
||||
<Play size={14} />
|
||||
Generate All Pages
|
||||
</button>
|
||||
)}
|
||||
<span className={`status-dot status-${bp.status}`}>{bp.status}</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Card>
|
||||
|
||||
<ProgressModal
|
||||
isOpen={showProgress}
|
||||
onClose={() => setShowProgress(false)}
|
||||
title="Generating Pages"
|
||||
message={isGenerating ? 'Generating content for all pages...' : 'Generation completed!'}
|
||||
progress={
|
||||
generationProgress
|
||||
? {
|
||||
current: generationProgress.pagesQueued,
|
||||
total: generationProgress.pagesQueued,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
taskId={generationProgress?.celeryTaskId}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
type StatItem,
|
||||
} from '@shared';
|
||||
import { useSiteDefinitionStore } from '../../state/siteDefinitionStore';
|
||||
import { useBuilderStore } from '../../state/builderStore';
|
||||
import type { PageBlock, PageBlueprint, SiteStructure } from '../../types/siteBuilder';
|
||||
|
||||
type StructuredContent = Record<string, unknown> & {
|
||||
@@ -20,6 +21,8 @@ type StructuredContent = Record<string, unknown> & {
|
||||
|
||||
export function PreviewCanvas() {
|
||||
const { structure, pages, selectedSlug, selectPage } = useSiteDefinitionStore();
|
||||
const { selectedPageIds, togglePageSelection, selectAllPages, clearPageSelection, activeBlueprint } =
|
||||
useBuilderStore();
|
||||
|
||||
const page = useMemo(() => {
|
||||
if (structure?.pages?.length) {
|
||||
@@ -66,9 +69,68 @@ export function PreviewCanvas() {
|
||||
</div>
|
||||
);
|
||||
|
||||
// Only show page selection if we have actual PageBlueprint objects with IDs
|
||||
const hasPageBlueprints = pages.length > 0 && pages.every((p) => p.id > 0);
|
||||
|
||||
const allSelected = hasPageBlueprints && pages.length > 0 && selectedPageIds.length === pages.length;
|
||||
const someSelected = hasPageBlueprints && selectedPageIds.length > 0 && selectedPageIds.length < pages.length;
|
||||
|
||||
return (
|
||||
<div className="preview-canvas">
|
||||
<div className="preview-nav">
|
||||
{hasPageBlueprints && activeBlueprint && (
|
||||
<div className="preview-page-selection" style={{ marginBottom: '12px', padding: '8px', background: '#f5f5f5', borderRadius: '4px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={allSelected}
|
||||
ref={(input) => {
|
||||
if (input) input.indeterminate = someSelected;
|
||||
}}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
selectAllPages();
|
||||
} else {
|
||||
clearPageSelection();
|
||||
}
|
||||
}}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
<label style={{ cursor: 'pointer', fontSize: '14px', fontWeight: '500' }}>
|
||||
Select pages for bulk generation ({selectedPageIds.length} selected)
|
||||
</label>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
|
||||
{pages.map((p) => {
|
||||
const isSelected = selectedPageIds.includes(p.id);
|
||||
return (
|
||||
<label
|
||||
key={p.id}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '12px',
|
||||
padding: '4px 8px',
|
||||
background: isSelected ? '#e3f2fd' : 'white',
|
||||
border: `1px solid ${isSelected ? '#2196f3' : '#ddd'}`,
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isSelected}
|
||||
onChange={() => togglePageSelection(p.id)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
<span>{p.title || p.slug.replace('-', ' ')}</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{navItems?.map((slug) => (
|
||||
<button
|
||||
key={slug}
|
||||
|
||||
Reference in New Issue
Block a user