feat: enhance news API schema with author and categories details,

desc: update dashboard to display author names, and improve pagination button styles
This commit is contained in:
fredy.siswanto 2025-03-06 22:49:56 +07:00
parent b61235a3b5
commit d3dc3b5016
9 changed files with 70 additions and 171 deletions

View File

@ -2,19 +2,26 @@ import { z } from 'zod'
import { HttpServer, type THttpServer } from '~/libs/http-server' import { HttpServer, type THttpServer } from '~/libs/http-server'
const authorSchema = z.object({
id: z.string(),
name: z.string(),
profile_picture: z.string(),
})
const categoriesCodeSchema = z.array(
z.object({
id: z.string(),
name: z.string(),
code: z.string(),
}),
)
const newsSchema = z.object({ const newsSchema = z.object({
data: z.array( data: z.array(
z.object({ z.object({
id: z.string(), id: z.string(),
title: z.string(), title: z.string(),
content: z.string(), content: z.string(),
categories: z.array( categories: categoriesCodeSchema,
z.object({
id: z.string(),
name: z.string(),
code: z.string(),
}),
),
tags: z.array( tags: z.array(
z.object({ z.object({
id: z.string(), id: z.string(),
@ -29,15 +36,14 @@ const newsSchema = z.object({
live_at: z.string(), live_at: z.string(),
created_at: z.string(), created_at: z.string(),
updated_at: z.string(), updated_at: z.string(),
author: z.object({ author: authorSchema,
id: z.string(),
name: z.string(),
profile_picture: z.string(),
}),
}), }),
), ),
}) })
export type TAuthor = z.infer<typeof authorSchema>
export type TCategories = z.infer<typeof categoriesCodeSchema>
export const getNews = async (parameters: THttpServer) => { export const getNews = async (parameters: THttpServer) => {
try { try {
const { data } = await HttpServer(parameters).get(`/api/news`) const { data } = await HttpServer(parameters).get(`/api/news`)

View File

@ -37,3 +37,39 @@ nav[aria-label='pagination'] {
display: flex; display: flex;
justify-content: center; justify-content: center;
} }
/* Style untuk tombol aktif (current) */
.dt-paging-button.current {
background-color: #2e2f7c !important;
color: white !important;
}
/* Style tombol aktif, kecuali jika disabled */
div.dt-container .dt-paging .dt-paging-button.current:not(.disabled),
div.dt-container .dt-paging .dt-paging-button.current:not(.disabled):hover {
color: white !important;
background-color: #2e2f7c !important;
min-width: 24px;
padding: 3px 6px;
}
/* Style tombol disabled */
div.dt-container .dt-paging .dt-paging-button.disabled {
background-color: transparent !important;
color: #ccc !important;
cursor: not-allowed;
pointer-events: none; /* Agar tidak bisa diklik */
}
/* Menghindari hover effect untuk tombol yang disabled */
div.dt-container .dt-paging .dt-paging-button:not(.disabled):hover {
background-color: #2e2f7c !important;
color: white !important;
}
/* Style default tombol */
div.dt-container .dt-paging .dt-paging-button {
min-width: 24px;
padding: 3px 6px;
background-color: transparent !important;
color: #2e2f7c !important;
}

View File

@ -3,8 +3,7 @@ import DataTable from 'datatables.net-react'
import React from 'react' import React from 'react'
export type UiTableProperties = { export type UiTableProperties = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any data: any // eslint-disable-line @typescript-eslint/no-explicit-any
data: any[]
columns: ConfigColumns[] columns: ConfigColumns[]
slots?: any // eslint-disable-line @typescript-eslint/no-explicit-any slots?: any // eslint-disable-line @typescript-eslint/no-explicit-any
options?: Config options?: Config

View File

@ -95,67 +95,6 @@ export const BERITA: TNews = {
], ],
} }
export const KAJIAN: TNews = {
title: 'KAJIAN',
description: DUMMY_DESCRIPTION,
items: [
{
title: 'Travelling as a way of self-discovery and progress ',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-2.jpg',
tags: ['Hukum Property'],
isPremium: true,
slug: 'travelling-as-a-way-of-self-discovery-and-progress ',
},
{
title: 'Travelling as a way of self-discovery and progress ',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-2.jpg',
tags: ['Hukum Property'],
isPremium: true,
slug: 'travelling-as-a-way-of-self-discovery-and-progress ',
},
{
title: 'Travelling as a way of self-discovery and progress ',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-2.jpg',
tags: ['Hukum Property'],
isPremium: true,
slug: 'travelling-as-a-way-of-self-discovery-and-progress ',
},
{
title: 'Travelling as a way of self-discovery and progress ',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: 'https://placehold.co/600x400.png',
tags: ['Hukum Property'],
isPremium: true,
slug: 'travelling-as-a-way-of-self-discovery-and-progress ',
},
{
title: 'How does writing influence your personal brand? ',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-3.jpg',
tags: ['Hukum Property'],
isPremium: true,
slug: 'how-does-writing-influence-your-personal-brand',
},
{
title: 'Helping a local business reinvent itself ',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-4.jpg',
tags: ['Hukum Property'],
isPremium: true,
slug: 'helping-a-local-business-reinvent-itself',
},
],
}
export const BANNER: TBanner[] = [ export const BANNER: TBanner[] = [
{ {
id: 1, id: 1,

View File

@ -2,11 +2,11 @@ import DT from 'datatables.net-dt'
import DataTable from 'datatables.net-react' import DataTable from 'datatables.net-react'
import { Link, useRouteLoaderData } from 'react-router' import { Link, useRouteLoaderData } from 'react-router'
import type { TAuthor, TCategories } from '~/apis/admin/get-news'
import { Button } from '~/components/ui/button' import { Button } from '~/components/ui/button'
import { UiTable } from '~/components/ui/table' import { UiTable } from '~/components/ui/table'
import { TitleDashboard } from '~/components/ui/title-dashboard' import { TitleDashboard } from '~/components/ui/title-dashboard'
import type { loader } from '~/routes/_admin.lg-admin._dashboard.contents' import type { loader } from '~/routes/_admin.lg-admin._dashboard.contents'
import type { TCategory } from '~/types/news'
import { formatDate } from '~/utils/formatter' import { formatDate } from '~/utils/formatter'
export const ContentsPage = () => { export const ContentsPage = () => {
@ -19,8 +19,8 @@ export const ContentsPage = () => {
const dataTable = newsData const dataTable = newsData
const dataColumns = [ const dataColumns = [
{ title: 'No', data: 'id' }, { title: 'No', data: 'id' },
{ title: 'Tanggal Konten', data: 'created_at' }, { title: 'Tanggal Konten', data: 'live_at' },
{ title: 'Nama Penulis', data: 'author_id' }, { title: 'Nama Penulis', data: 'author' },
{ title: 'Judul', data: 'title' }, { title: 'Judul', data: 'title' },
{ title: 'Kategori', data: 'categories' }, { title: 'Kategori', data: 'categories' },
{ {
@ -34,18 +34,17 @@ export const ContentsPage = () => {
}, },
{ {
title: 'Action', title: 'Action',
data: 'id', data: 'slug',
}, },
] ]
const dataSlot = { const dataSlot = {
1: (value: string) => { 1: (value: string) => {
return formatDate(value) return formatDate(value)
}, },
2: (value: TAuthor) => `${value.name}`,
4: (value: TCategory[]) => { 4: (value: TCategories) => {
return value.map((item: { name: string }) => { const categories = value.map((item) => item.name).join(', ')
return `${item.name}` return `${categories}`
})
}, },
6: (value: string | number) => { 6: (value: string | number) => {
return ( return (

View File

@ -1,5 +1,4 @@
import { DUMMY_DESCRIPTION } from '~/data/contents' import type { TNewsDetail } from '~/types/news'
import type { TNews, TNewsDetail } from '~/types/news'
export const CONTENT: TNewsDetail = { export const CONTENT: TNewsDetail = {
title: 'Hotman Paris Membuka Perpustakaan di tengah Diskotik', title: 'Hotman Paris Membuka Perpustakaan di tengah Diskotik',
@ -38,35 +37,3 @@ export const CONTENT: TNewsDetail = {
categories: [], categories: [],
tags: ['Category', 'Popular', 'Trending', 'Latest'], tags: ['Category', 'Popular', 'Trending', 'Latest'],
} }
export const BERITA: TNews = {
title: 'BERITA',
description: DUMMY_DESCRIPTION,
items: [
{
title: 'Travelling as a way of self-discovery and progress',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-2.jpg',
tags: ['Hukum Property'],
slug: 'travelling-as-a-way-of-self-discovery-and-progress',
},
{
title: 'How does writing influence your personal brand?',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-3.jpg',
tags: ['Hukum'],
slug: 'how-does-writing-influence-your-personal-brand',
},
{
title: 'Helping a local business reinvent itself',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-4.jpg',
tags: ['Hukum Property'],
isPremium: true,
slug: 'helping-a-local-business-reinvent-itself',
},
],
}

View File

@ -34,7 +34,7 @@ export const NewsDetailPage = () => {
</p> </p>
</div> </div>
</div> </div>
<IconsSocial className="flex-row-reverse" /> <IconsSocial className="flex-row" />
</div> </div>
{/* end next planing create component for this section */} {/* end next planing create component for this section */}
<div className="w-full bg-amber-200"> <div className="w-full bg-amber-200">
@ -50,7 +50,7 @@ export const NewsDetailPage = () => {
{htmlParse(content)} {htmlParse(content)}
</article> </article>
</div> </div>
<div className="items-end justify-between border-b-3 border-b-gray-300 py-4 sm:flex"> <div className="items-end justify-between border-b-gray-300 py-4 sm:flex">
<div className="flex flex-col max-sm:mb-3"> <div className="flex flex-col max-sm:mb-3">
<p className="mb-2">Share this post</p> <p className="mb-2">Share this post</p>
<IconsSocial className="a" /> <IconsSocial className="a" />
@ -66,18 +66,6 @@ export const NewsDetailPage = () => {
))} ))}
</div> </div>
</div> </div>
<div className="mt-5 flex items-center gap-2 align-middle">
<img
src={'https://placehold.co/50x50.png'}
alt={title}
className="h-12 w-12 rounded-full"
/>
<div>
<h4 className="text-md">{author}</h4>
<p className="text-sm">Job title, Company name</p>
</div>
</div>
</div> </div>
</Card> </Card>

View File

@ -18,41 +18,3 @@ export type TNewsDetail = {
isPremium?: boolean isPremium?: boolean
categories?: Array<string> categories?: Array<string>
} }
export type TTag = {
id: string
code: string
name: string
created_at: string
updated_at: string
}
export type TCategory = {
id: string
name: string
code: string
created_at: string
updated_at: string
}
export type Author = {
id: string
name: string
profile_picture: string
}
export type TKontents = {
id: string
title: string
content: string
featured_image: string
tags: TTag[]
categories: TCategory[]
is_premium: boolean
slug: string
author_id: string
live_at: string
created_at: string
updated_at: string
author: Author
}

View File

@ -12,7 +12,10 @@ type TGetPremiumAttribute = {
export const getPremiumAttribute = (parameters: TGetPremiumAttribute) => { export const getPremiumAttribute = (parameters: TGetPremiumAttribute) => {
const { isPremium, slug, onClick, userData } = parameters const { isPremium, slug, onClick, userData } = parameters
if (isPremium && (!userData || userData?.subscribe_plan_code === 'basic')) { if (
isPremium &&
(!userData || userData?.subscribe.subscribe_plan.code === 'basic')
) {
return { return {
onClick, onClick,
to: '', to: '',