update: group templates as in built and ai custom in ui
This commit is contained in:
parent
1158fdb7ec
commit
0501150109
4 changed files with 144 additions and 52 deletions
|
|
@ -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
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue