diff --git a/messages/en.json b/messages/en.json index 47f81e2..03571a1 100644 --- a/messages/en.json +++ b/messages/en.json @@ -328,7 +328,8 @@ "marketing": "Marketing", "crm": "CRM", "ranking": "Ranking", - "stats": "Dashboard" + "stats": "Dashboard", + "music": "Music" }, "appAdminNodesPage": { "title": "Render Nodes", diff --git a/messages/fa.json b/messages/fa.json index 5acae1e..1999fe1 100644 --- a/messages/fa.json +++ b/messages/fa.json @@ -328,7 +328,8 @@ "marketing": "بازاریابی", "crm": "مدیریت مشتریان", "ranking": "رتبه‌بندی", - "stats": "داشبورد" + "stats": "داشبورد", + "music": "موسیقی" }, "appAdminNodesPage": { "title": "نودهای رندر", diff --git a/services/identity/FlatRender.IdentitySvc/Application/Services/AdminService.cs b/services/identity/FlatRender.IdentitySvc/Application/Services/AdminService.cs index 3bdc5cb..7d579cb 100644 --- a/services/identity/FlatRender.IdentitySvc/Application/Services/AdminService.cs +++ b/services/identity/FlatRender.IdentitySvc/Application/Services/AdminService.cs @@ -13,8 +13,9 @@ public class AdminService(IdentityDbContext db) public async Task GetCrmAnalyticsAsync(Guid tenantId, DateTime start, DateTime end) { - start = start.Date; - end = end.Date.AddDays(1); // inclusive of the end day + // Query-bound dates arrive Kind=Unspecified; Npgsql requires UTC for timestamptz. + start = DateTime.SpecifyKind(start.Date, DateTimeKind.Utc); + end = DateTime.SpecifyKind(end.Date.AddDays(1), DateTimeKind.Utc); // inclusive of the end day if ((end - start).TotalDays > 366) end = start.AddDays(366); var signups = await db.Users diff --git a/src/app/[locale]/admin/layout.tsx b/src/app/[locale]/admin/layout.tsx index 365ec58..98475e3 100644 --- a/src/app/[locale]/admin/layout.tsx +++ b/src/app/[locale]/admin/layout.tsx @@ -22,6 +22,7 @@ export default async function AdminLayout({ { href: "/admin/ranking", label: t("ranking") }, { href: "/admin/tags", label: t("tags") }, { href: "/admin/fonts", label: t("fonts") }, + { href: "/admin/music", label: t("music") }, { href: "/admin/blogs", label: t("blogs") }, { href: "/admin/slides", label: t("slides") }, { href: "/admin/files", label: t("media") }, diff --git a/src/app/[locale]/admin/music/page.tsx b/src/app/[locale]/admin/music/page.tsx new file mode 100644 index 0000000..f139600 --- /dev/null +++ b/src/app/[locale]/admin/music/page.tsx @@ -0,0 +1,8 @@ +"use client"; + +import { AdminResource } from "@/components/admin/AdminResource"; +import { musicConfig } from "@/components/admin/admin-resources"; + +export default function Page() { + return ; +} diff --git a/src/components/admin/admin-resources.tsx b/src/components/admin/admin-resources.tsx index f2a2678..a1da783 100644 --- a/src/components/admin/admin-resources.tsx +++ b/src/components/admin/admin-resources.tsx @@ -109,6 +109,34 @@ export const fontsConfig: ResourceConfig = { ], }; +export const musicConfig: ResourceConfig = { + title: "Music", + description: "Audio tracks available in the studio music library.", + basePath: "music", + listKey: "items", + canCreate: true, + canEdit: false, + canDelete: true, + columns: [ + { key: "name", label: "Name" }, + { key: "genre", label: "Genre" }, + { key: "mood", label: "Mood" }, + { key: "duration_sec", label: "Duration (s)" }, + { key: "is_premium", label: "Premium", render: (r) => badge(!!r.is_premium, "premium", "free") }, + ], + fields: [ + { key: "name", label: "Name", required: true }, + { key: "caption", label: "Caption" }, + { key: "keywords", label: "Keywords" }, + { key: "url", label: "Audio file", type: "file", required: true }, + { key: "duration_sec", label: "Duration (seconds)", type: "number", required: true }, + { key: "bpm", label: "BPM", type: "number" }, + { key: "genre", label: "Genre" }, + { key: "mood", label: "Mood" }, + { key: "is_premium", label: "Premium", type: "checkbox" }, + ], +}; + export const blogsConfig: ResourceConfig = { title: "Blog Posts", description: "CMS articles (also created by the AI SEO generator).",