diff --git a/servers/fastapi/api/v1/ppt/endpoints/slide_to_html.py b/servers/fastapi/api/v1/ppt/endpoints/slide_to_html.py index 67539938..bb9f3157 100644 --- a/servers/fastapi/api/v1/ppt/endpoints/slide_to_html.py +++ b/servers/fastapi/api/v1/ppt/endpoints/slide_to_html.py @@ -79,6 +79,7 @@ class GetLayoutsResponse(BaseModel): class PresentationSummary(BaseModel): presentation_id: str layout_count: int + last_updated_at: Optional[datetime] = None class GetPresentationSummaryResponse(BaseModel): @@ -830,21 +831,13 @@ async def get_presentations_summary( ): """ Get summary of all presentations with their layout counts. - - Args: - session: Database session - - Returns: - GetPresentationSummaryResponse with list of presentations and their layout counts - - Raises: - HTTPException: 500 for server errors """ try: - # Query to get presentation_id and count of layouts grouped by presentation_id + # Query to get presentation_id, count of layouts, and MAX(updated_at) stmt = select( PresentationLayoutCodeModel.presentation_id, - func.count(PresentationLayoutCodeModel.id).label('layout_count') + func.count(PresentationLayoutCodeModel.id).label('layout_count'), + func.max(PresentationLayoutCodeModel.updated_at).label('last_updated_at') ).group_by(PresentationLayoutCodeModel.presentation_id) result = await session.execute(stmt) @@ -854,7 +847,8 @@ async def get_presentations_summary( presentations = [ PresentationSummary( presentation_id=row.presentation_id, - layout_count=row.layout_count + layout_count=row.layout_count, + last_updated_at=row.last_updated_at ) for row in presentation_data ] diff --git a/servers/nextjs/app/(presentation-generator)/dashboard/components/DashboardPage.tsx b/servers/nextjs/app/(presentation-generator)/dashboard/components/DashboardPage.tsx index 70a70d97..41fabd4c 100644 --- a/servers/nextjs/app/(presentation-generator)/dashboard/components/DashboardPage.tsx +++ b/servers/nextjs/app/(presentation-generator)/dashboard/components/DashboardPage.tsx @@ -6,7 +6,7 @@ import Wrapper from "@/components/Wrapper"; import { DashboardApi } from "@/app/(presentation-generator)/services/api/dashboard"; import { PresentationGrid } from "@/app/(presentation-generator)/dashboard/components/PresentationGrid"; -import Header from "@/components/Header"; +import Header from "@/app/(presentation-generator)/dashboard/components/Header"; const DashboardPage: React.FC = () => { const [presentations, setPresentations] = useState(null); diff --git a/servers/nextjs/app/(presentation-generator)/outline/components/LayoutSelection.tsx b/servers/nextjs/app/(presentation-generator)/outline/components/LayoutSelection.tsx index e5538cff..d0d01a81 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/components/LayoutSelection.tsx +++ b/servers/nextjs/app/(presentation-generator)/outline/components/LayoutSelection.tsx @@ -20,17 +20,35 @@ const LayoutSelection: React.FC = ({ loading } = useLayout(); + const [summaryMap, setSummaryMap] = React.useState>({}); + + useEffect(() => { + // Fetch custom templates summary to get last_updated_at for sorting + fetch("/api/v1/ppt/layout-management/summary") + .then(res => res.json()) + .then(data => { + const map: Record = {}; + if (data && Array.isArray(data.presentations)) { + for (const p of data.presentations) { + // groups are named custom- + map[`custom-${p.presentation_id}`] = p.last_updated_at ? new Date(p.last_updated_at).getTime() : 0; + } + } + setSummaryMap(map); + }) + .catch(() => setSummaryMap({})); + }, []); + const layoutGroups: LayoutGroup[] = React.useMemo(() => { const groups = getAllGroups(); if (groups.length === 0) return []; const Groups: LayoutGroup[] = groups.map(groupName => { - const settings = getGroupSetting(groupName); return { id: groupName, name: groupName, - description: settings?.description || `${groupName} presentation layouts`, + description: settings?.description || `${groupName} presentation templates`, ordered: settings?.ordered || false, default: settings?.default || false, }; @@ -44,6 +62,16 @@ const LayoutSelection: React.FC = ({ }); }, [getAllGroups, getLayoutsByGroup, getGroupSetting]); + const inBuiltGroups = React.useMemo( + () => layoutGroups.filter(g => !g.name.toLowerCase().startsWith("custom-")), + [layoutGroups] + ); + const customGroups = React.useMemo(() => { + const unsorted = layoutGroups.filter(g => g.name.toLowerCase().startsWith("custom-")); + // Sort by last_updated_at desc using summaryMap + return unsorted.sort((a, b) => (summaryMap[b.name] || 0) - (summaryMap[a.name] || 0)); + }, [layoutGroups, summaryMap]); + // Auto-select first group when groups are loaded useEffect(() => { if (layoutGroups.length > 0 && !selectedLayoutGroup) { @@ -101,16 +129,37 @@ const LayoutSelection: React.FC = ({ } return ( -
-
- {layoutGroups.map((group) => ( - - ))} +
+ {/* In Built Templates */} +
+

In Built Templates

+
+ {inBuiltGroups.map((group) => ( + + ))} +
+
+ + {/* Custom AI Templates */} +
+
+

Custom AI Templates

+
+
+ {customGroups.map((group) => ( + + ))} +
); diff --git a/servers/nextjs/app/(presentation-generator)/template-preview/page.tsx b/servers/nextjs/app/(presentation-generator)/template-preview/page.tsx index 2938de74..85135158 100644 --- a/servers/nextjs/app/(presentation-generator)/template-preview/page.tsx +++ b/servers/nextjs/app/(presentation-generator)/template-preview/page.tsx @@ -37,6 +37,13 @@ const LayoutPreview = () => { settings: getGroupSetting(groupName) || { description: "", ordered: false }, })); + const inBuiltGroups = layoutGroups.filter( + (g) => !g.groupName.toLowerCase().startsWith("custom-") + ); + const customGroups = layoutGroups.filter((g) => + g.groupName.toLowerCase().startsWith("custom-") + ); + // Handle loading state if (loading) { return ; @@ -65,17 +72,16 @@ const LayoutPreview = () => {
- {/* Group Navigation Cards */} -
-
+ {/* In Built Templates */} +
+
+

In Built Templates

- {layoutGroups.map((group) => ( + {inBuiltGroups.map((group) => ( - router.push(`/template-preview/${group.groupName}`) - } + onClick={() => router.push(`/template-preview/${group.groupName}`)} >
@@ -106,28 +112,71 @@ const LayoutPreview = () => {
))} - router.push(`/custom-layout`)} - > -
-
-

- Create -

-
- -
-
-

- Create a new custom layout -

-
-
-
- {/* */} +
+ + {/* Custom Templates */} +
+
+
+

Custom AI Templates

+
+
+ {customGroups.length > 0 ? ( + customGroups.map((group) => ( + router.push(`/template-preview/${group.groupName}`)} + > +
+
+

+ {group.groupName} +

+
+ + {group.layouts.length} + + +
+
+

+ {group.settings.description} +

+
+ + {group.layouts.length} layout + {group.layouts.length !== 1 ? "s" : ""} + +
+
+
+ )) + ) : ( + router.push(`/custom-template`)} + > +
+
+

+ Create +

+
+ +
+
+

+ Create your first custom AI template +

+
+
+ )} +
+
+
);