update: group templates as in built and ai custom in ui

This commit is contained in:
Suraj Jha 2025-08-09 22:30:35 +05:45
parent 1158fdb7ec
commit 0501150109
No known key found for this signature in database
GPG key ID: 5AC6C16355CE2C14
4 changed files with 144 additions and 52 deletions

View file

@ -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
]

View file

@ -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<any>(null);

View file

@ -20,17 +20,35 @@ const LayoutSelection: React.FC<LayoutSelectionProps> = ({
loading
} = useLayout();
const [summaryMap, setSummaryMap] = React.useState<Record<string, number>>({});
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<string, number> = {};
if (data && Array.isArray(data.presentations)) {
for (const p of data.presentations) {
// groups are named custom-<presentation_id>
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<LayoutSelectionProps> = ({
});
}, [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<LayoutSelectionProps> = ({
}
return (
<div className="space-y-6 mb-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{layoutGroups.map((group) => (
<GroupLayouts
key={group.id}
group={group}
onSelectLayoutGroup={handleLayoutGroupSelection}
selectedLayoutGroup={selectedLayoutGroup}
/>
))}
<div className="space-y-8 mb-4">
{/* In Built Templates */}
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-3">In Built Templates</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{inBuiltGroups.map((group) => (
<GroupLayouts
key={group.id}
group={group}
onSelectLayoutGroup={handleLayoutGroupSelection}
selectedLayoutGroup={selectedLayoutGroup}
/>
))}
</div>
</div>
{/* Custom AI Templates */}
<div>
<div className="flex items-center justify-between mb-3">
<h3 className="text-lg font-semibold text-gray-900">Custom AI Templates</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{customGroups.map((group) => (
<GroupLayouts
key={group.id}
group={group}
onSelectLayoutGroup={handleLayoutGroupSelection}
selectedLayoutGroup={selectedLayoutGroup}
/>
))}
</div>
</div>
</div>
);

View file

@ -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 <LoadingStates type="loading" />;
@ -65,17 +72,16 @@ const LayoutPreview = () => {
</div>
</div>
{/* Group Navigation Cards */}
<div className=" h-full pt-16 flex justify-center items-center">
<div className="max-w-7xl mx-auto px-6 py-6">
{/* In Built Templates */}
<section className="h-full pt-16 flex justify-center items-center">
<div className="max-w-7xl mx-auto px-6 py-6 w-full">
<h2 className="text-xl font-semibold text-gray-900 mb-4">In Built Templates</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{layoutGroups.map((group) => (
{inBuiltGroups.map((group) => (
<Card
key={group.groupName}
className="cursor-pointer hover:shadow-md transition-all duration-200 group"
onClick={() =>
router.push(`/template-preview/${group.groupName}`)
}
onClick={() => router.push(`/template-preview/${group.groupName}`)}
>
<div className="p-6">
<div className="flex items-center justify-between mb-3">
@ -106,28 +112,71 @@ const LayoutPreview = () => {
</div>
</Card>
))}
<Card
className="cursor-pointer hover:shadow-md transition-all border-blue-500 duration-200 group"
onClick={() => router.push(`/custom-layout`)}
>
<div className="p-6">
<div className="flex items-center justify-between mb-3">
<h3 className="text-lg font-semibold text-gray-900 capitalize group-hover:text-blue-600 transition-colors">
Create
</h3>
<div className="flex items-center gap-2">
<ExternalLink className="w-4 h-4 text-gray-400 group-hover:text-blue-600 transition-colors" />
</div>
</div>
<p className="text-sm text-gray-600 mb-4">
Create a new custom layout
</p>
</div>
</Card>
</div>
</div>
</div>
{/* <CustomLayout /> */}
</section>
{/* Custom Templates */}
<section className="h-full pt-8 pb-16 flex justify-center items-center">
<div className="max-w-7xl mx-auto px-6 py-6 w-full">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-semibold text-gray-900">Custom AI Templates</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{customGroups.length > 0 ? (
customGroups.map((group) => (
<Card
key={group.groupName}
className="cursor-pointer hover:shadow-md transition-all duration-200 group"
onClick={() => router.push(`/template-preview/${group.groupName}`)}
>
<div className="p-6">
<div className="flex items-center justify-between mb-3">
<h3 className="text-lg font-semibold text-gray-900 capitalize group-hover:text-blue-600 transition-colors">
{group.groupName}
</h3>
<div className="flex items-center gap-2">
<span className="px-2 py-1 bg-blue-100 text-blue-800 rounded-full text-xs font-medium">
{group.layouts.length}
</span>
<ExternalLink className="w-4 h-4 text-gray-400 group-hover:text-blue-600 transition-colors" />
</div>
</div>
<p className="text-sm text-gray-600 mb-4">
{group.settings.description}
</p>
<div className="flex items-center justify-between">
<span className="text-xs text-gray-500">
{group.layouts.length} layout
{group.layouts.length !== 1 ? "s" : ""}
</span>
</div>
</div>
</Card>
))
) : (
<Card
className="cursor-pointer hover:shadow-md transition-all border-blue-500 duration-200 group"
onClick={() => router.push(`/custom-template`)}
>
<div className="p-6">
<div className="flex items-center justify-between mb-3">
<h3 className="text-lg font-semibold text-gray-900 capitalize group-hover:text-blue-600 transition-colors">
Create
</h3>
<div className="flex items-center gap-2">
<ExternalLink className="w-4 h-4 text-gray-400 group-hover:text-blue-600 transition-colors" />
</div>
</div>
<p className="text-sm text-gray-600 mb-4">
Create your first custom AI template
</p>
</div>
</Card>
)}
</div>
</div>
</section>
</div>
</div>
);