feat: Page Editor v1.0 - canonical schema, SSR parity, and migration
Major improvements to WooNooW Page Editor system: Schema & Architecture: - Canonical section schema with unified sectionSchema.ts - Normalized feature-grid to use items (not features) - Standardized default values across all section types - Schema versioning with automatic migration on read Backend (PHP): - Enhanced PlaceholderRenderer with typed output contracts - Added fallback behavior for empty/invalid dynamic sources - Added caching support for post data resolution - New SchemaMigration class for backward compatibility - New Features class for feature flags - Enhanced PageSSR with full style support - Removed controller-level special-casing for related_posts Frontend (Admin SPA): - Updated CanvasRenderer with schema-aware transformation - Enhanced InspectorPanel with canonical schema metadata - Added new section renderers Frontend (Customer SPA): - New section components: BentoCategoryGrid, MarqueeBanner, ProductCarousel, ShoppableImage - Updated FeatureGridSection for items prop contract Testing: - Add PHP tests: SchemaMigrationTest, PlaceholderRendererTest, PageSSRTest - Add TypeScript tests: schema-integration, feature-grid-regression - Add parity tests for React vs SSR content matching - Add CI script: check-schema-drift.mjs - Add VERIFICATION_CHECKLIST.md Documentation: - RELEASE_NOTES-v1.0.md with full release notes - docs/PAGE_EDITOR_SECTION_SCHEMA_V1.md - docs/PAGE_EDITOR_SSR_COVERAGE_AUDIT.md
This commit is contained in:
48
admin-spa/src/components/Pagination.tsx
Normal file
48
admin-spa/src/components/Pagination.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { __ } from '@/lib/i18n';
|
||||
|
||||
interface PaginationProps {
|
||||
page: number;
|
||||
perPage: number;
|
||||
total: number;
|
||||
onPageChange: (newPage: number) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Pagination({ page, perPage, total, onPageChange, className = '' }: PaginationProps) {
|
||||
if (total <= perPage) return null;
|
||||
|
||||
const startItem = ((page - 1) * perPage) + 1;
|
||||
const endItem = Math.min(page * perPage, total);
|
||||
const totalPages = Math.ceil(total / perPage);
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col sm:flex-row justify-between items-center gap-4 pt-4 ${className}`}>
|
||||
<div className="text-sm text-muted-foreground order-2 sm:order-1">
|
||||
{__('Showing')} {startItem} - {endItem} {__('of')} {total}
|
||||
</div>
|
||||
<div className="flex gap-2 order-1 sm:order-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onPageChange(Math.max(1, page - 1))}
|
||||
disabled={page <= 1}
|
||||
>
|
||||
{__('Previous')}
|
||||
</Button>
|
||||
<div className="flex items-center sm:hidden text-sm opacity-80 px-2">
|
||||
{__('Page')} {page} {__('of')} {totalPages}
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onPageChange(page + 1)}
|
||||
disabled={page >= totalPages}
|
||||
>
|
||||
{__('Next')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user