From e7eda086e42016847d9d1c943d6accbcc9e5d15b Mon Sep 17 00:00:00 2001 From: Ardeman Date: Sun, 9 Mar 2025 14:32:04 +0800 Subject: [PATCH 1/6] refactor: integrate autoplay functionality in Banner and enhance news data handling --- app/components/ui/banner.tsx | 3 ++- app/pages/news/index.tsx | 11 ++++++++--- app/routes/_news._index.tsx | 18 +++++++++++++++++- package.json | 1 + pnpm-lock.yaml | 12 ++++++++++++ 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/app/components/ui/banner.tsx b/app/components/ui/banner.tsx index 354f5e7..0ba09ec 100644 --- a/app/components/ui/banner.tsx +++ b/app/components/ui/banner.tsx @@ -1,10 +1,11 @@ +import Autoplay from 'embla-carousel-autoplay' import useEmblaCarousel from 'embla-carousel-react' import { Link } from 'react-router' import { BANNER } from '~/data/contents' export const Banner = () => { - const [emblaReference] = useEmblaCarousel({ loop: false }) + const [emblaReference] = useEmblaCarousel({ loop: true }, [Autoplay()]) return (
diff --git a/app/pages/news/index.tsx b/app/pages/news/index.tsx index 283c05b..2625511 100644 --- a/app/pages/news/index.tsx +++ b/app/pages/news/index.tsx @@ -19,6 +19,11 @@ export const NewsPage = () => { description: loaderData?.beritaCategory?.description || '', items: loaderData?.beritaNews || [], } + const kajian: TNews = { + title: loaderData?.kajianCategory?.name || '', + description: loaderData?.kajianCategory?.description || '', + items: loaderData?.kajianNews || [], + } return (
@@ -32,9 +37,9 @@ export const NewsPage = () => { - {/* - - */} + + +
) } diff --git a/app/routes/_news._index.tsx b/app/routes/_news._index.tsx index 3ac1ba8..6b1e187 100644 --- a/app/routes/_news._index.tsx +++ b/app/routes/_news._index.tsx @@ -6,17 +6,33 @@ import type { Route } from './+types/_news._index' export const loader = async ({}: Route.LoaderArgs) => { const { data: categoriesData } = await getCategories() + const spotlightCode = 'spotlight' const spotlightCategory = categoriesData.find( (category) => category.code === spotlightCode, ) const { data: spotlightNews } = await getNews({ categories: [spotlightCode] }) + const beritaCode = 'berita' const beritaCategory = categoriesData.find( (category) => category.code === beritaCode, ) const { data: beritaNews } = await getNews({ categories: [beritaCode] }) - return { spotlightCategory, spotlightNews, beritaCategory, beritaNews } + + const kajianCode = 'kajian' + const kajianCategory = categoriesData.find( + (category) => category.code === kajianCode, + ) + const { data: kajianNews } = await getNews({ categories: [kajianCode] }) + + return { + spotlightCategory, + spotlightNews, + beritaCategory, + beritaNews, + kajianCategory, + kajianNews, + } } const NewsIndexLayout = () => diff --git a/package.json b/package.json index 5f93d18..f94c5fa 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "class-variance-authority": "^0.7.1", "datatables.net-dt": "^2.2.2", "datatables.net-react": "^1.0.0", + "embla-carousel-autoplay": "^8.5.2", "embla-carousel-react": "^8.5.2", "html-react-parser": "^5.2.2", "isbot": "^5.1.17", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9c10c9f..973cf7c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,9 @@ importers: datatables.net-react: specifier: ^1.0.0 version: 1.0.0 + embla-carousel-autoplay: + specifier: ^8.5.2 + version: 8.5.2(embla-carousel@8.5.2) embla-carousel-react: specifier: ^8.5.2 version: 8.5.2(react@19.0.0) @@ -2331,6 +2334,11 @@ packages: electron-to-chromium@1.5.90: resolution: {integrity: sha512-C3PN4aydfW91Natdyd449Kw+BzhLmof6tzy5W1pFC5SpQxVXT+oyiyOG9AgYYSN9OdA/ik3YkCrpwqI8ug5Tug==} + embla-carousel-autoplay@8.5.2: + resolution: {integrity: sha512-27emJ0px3q/c0kCHCjwRrEbYcyYUPfGO3g5IBWF1i7714TTzE6L9P81V6PHLoSMAKJ1aHoT2e7YFOsuFKCbyag==} + peerDependencies: + embla-carousel: 8.5.2 + embla-carousel-react@8.5.2: resolution: {integrity: sha512-Tmx+uY3MqseIGdwp0ScyUuxpBgx5jX1f7od4Cm5mDwg/dptEiTKf9xp6tw0lZN2VA9JbnVMl/aikmbc53c6QFA==} peerDependencies: @@ -6990,6 +6998,10 @@ snapshots: electron-to-chromium@1.5.90: {} + embla-carousel-autoplay@8.5.2(embla-carousel@8.5.2): + dependencies: + embla-carousel: 8.5.2 + embla-carousel-react@8.5.2(react@19.0.0): dependencies: embla-carousel: 8.5.2 From 9f6c07c3de12e283c5802c8243fd15a92abb92c9 Mon Sep 17 00:00:00 2001 From: Ardeman Date: Sun, 9 Mar 2025 14:54:18 +0800 Subject: [PATCH 2/6] refactor: rename 'Konten' to 'Artikel' and add submenu for article management --- app/components/ui/chart.tsx | 2 +- app/configs/meta.ts | 3 ++- app/layouts/admin/menu.ts | 13 ++++++++++++- app/pages/dashboard-contents/index.tsx | 4 ++-- app/pages/form-contents/index.tsx | 2 +- app/pages/news-detail/index.tsx | 4 +--- 6 files changed, 19 insertions(+), 9 deletions(-) diff --git a/app/components/ui/chart.tsx b/app/components/ui/chart.tsx index 25c38dd..3915571 100644 --- a/app/components/ui/chart.tsx +++ b/app/components/ui/chart.tsx @@ -71,7 +71,7 @@ export const UiChartPie = () => { return (
-

Top 5 Konten

+

Top 5 Artikel

menu.items.map((item) => ({ path: item.url, title: item.title })), ), + ...SUB_MENU, ] diff --git a/app/layouts/admin/menu.ts b/app/layouts/admin/menu.ts index 36f57b7..69e18cb 100644 --- a/app/layouts/admin/menu.ts +++ b/app/layouts/admin/menu.ts @@ -30,7 +30,7 @@ export const MENU: TMenu[] = [ icon: DocumentIcon, }, { - title: 'Konten', + title: 'Artikel', url: '/lg-admin/contents', icon: ChatIcon, }, @@ -62,3 +62,14 @@ export const MENU: TMenu[] = [ ], }, ] + +export const SUB_MENU = [ + { + title: 'Buat Artikel', + path: '/lg-admin/contents/create', + }, + { + title: 'Update Artikel', + path: '/lg-admin/contents/update', + }, +] diff --git a/app/pages/dashboard-contents/index.tsx b/app/pages/dashboard-contents/index.tsx index f6092fc..450b7d1 100644 --- a/app/pages/dashboard-contents/index.tsx +++ b/app/pages/dashboard-contents/index.tsx @@ -98,7 +98,7 @@ export const ContentsPage = () => { return (
- +
{/* TODO: Filter */}
) diff --git a/app/pages/form-contents/index.tsx b/app/pages/form-contents/index.tsx index 6ad19e0..8e60d0e 100644 --- a/app/pages/form-contents/index.tsx +++ b/app/pages/form-contents/index.tsx @@ -207,7 +207,7 @@ export const FormContentsPage = (properties: TProperties) => { label="Konten" placeholder="Masukkan Konten" className="shadow" - inputClassName="bg-white focus:ring-1 focus:ring-[#2E2F7C] focus:outline-none border-0" + inputClassName="bg-white focus:ring-1 focus:ring-[#2E2F7C] focus:outline-none border-0 min-h-[42px]" labelClassName="text-sm font-medium text-[#363636]" category="content" /> diff --git a/app/pages/news-detail/index.tsx b/app/pages/news-detail/index.tsx index 84fb4c6..8bcd236 100644 --- a/app/pages/news-detail/index.tsx +++ b/app/pages/news-detail/index.tsx @@ -29,9 +29,7 @@ export const NewsDetailPage = () => {
-

- {title} -

+

{title}

Date: Sun, 9 Mar 2025 14:55:04 +0800 Subject: [PATCH 3/6] refactor: update placeholder text for featured image input to clarify URL requirement --- app/pages/form-contents/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pages/form-contents/index.tsx b/app/pages/form-contents/index.tsx index 8e60d0e..1953bcf 100644 --- a/app/pages/form-contents/index.tsx +++ b/app/pages/form-contents/index.tsx @@ -136,7 +136,7 @@ export const FormContentsPage = (properties: TProperties) => { Date: Sun, 9 Mar 2025 15:14:57 +0800 Subject: [PATCH 4/6] feat: add InputFile component for file uploads and integrate into FormContentsPage --- app/components/ui/input-file.tsx | 80 +++++++++++++++++++++++++++++++ app/components/ui/input.tsx | 2 +- app/pages/form-contents/index.tsx | 3 +- 3 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 app/components/ui/input-file.tsx diff --git a/app/components/ui/input-file.tsx b/app/components/ui/input-file.tsx new file mode 100644 index 0000000..c6bb7d8 --- /dev/null +++ b/app/components/ui/input-file.tsx @@ -0,0 +1,80 @@ +import { Field, Label, Input as HeadlessInput } from '@headlessui/react' +import { CloudArrowUpIcon } from '@heroicons/react/20/solid' +import { type ComponentProps, type ReactNode } from 'react' +import { + get, + type FieldError, + type FieldValues, + type Path, + type RegisterOptions, +} from 'react-hook-form' +import { useRemixFormContext } from 'remix-hook-form' +import { twMerge } from 'tailwind-merge' + +import { Button } from './button' + +type TInputProperties = Omit< + ComponentProps<'input'>, + 'size' +> & { + id: string + label?: ReactNode + name: Path + rules?: RegisterOptions + containerClassName?: string + labelClassName?: string +} + +export const InputFile = >( + properties: TInputProperties, +) => { + const { + id, + label, + name, + rules, + placeholder, + disabled, + className, + containerClassName, + labelClassName, + ...restProperties + } = properties + + const { + register, + formState: { errors }, + } = useRemixFormContext() + + const error: FieldError = get(errors, name) + + return ( + + + + + + ) +} diff --git a/app/components/ui/input.tsx b/app/components/ui/input.tsx index f4d73f1..ee6fb6a 100644 --- a/app/components/ui/input.tsx +++ b/app/components/ui/input.tsx @@ -75,7 +75,7 @@ export const Input = >( type="button" variant="icon" size="fit" - className="absolute right-3 h-[42px] text-gray-500" + className="absolute right-3 h-[42px]" onClick={() => setInputType(inputType === 'password' ? 'text' : 'password') } diff --git a/app/pages/form-contents/index.tsx b/app/pages/form-contents/index.tsx index 1953bcf..1e12a67 100644 --- a/app/pages/form-contents/index.tsx +++ b/app/pages/form-contents/index.tsx @@ -10,6 +10,7 @@ import { TextEditor } from '~/components/text-editor' import { Button } from '~/components/ui/button' import { Combobox } from '~/components/ui/combobox' import { Input } from '~/components/ui/input' +import { InputFile } from '~/components/ui/input-file' import { Switch } from '~/components/ui/switch' import { TitleDashboard } from '~/components/ui/title-dashboard' import type { loader } from '~/routes/_admin.lg-admin._dashboard' @@ -133,7 +134,7 @@ export const FormContentsPage = (properties: TProperties) => { containerClassName="flex-1" disabled={!!newsData} /> - Date: Sun, 9 Mar 2025 15:31:10 +0800 Subject: [PATCH 5/6] refactor: update table component types and enhance admin menu with category and tag --- app/components/ui/table.tsx | 6 ++-- app/layouts/admin/menu.ts | 16 ++++++++++ app/pages/dashboard-advertisements/index.tsx | 6 ++-- app/pages/dashboard-categories/index.tsx | 21 ++++++------- app/pages/dashboard-contents/index.tsx | 24 ++++++++------- app/pages/dashboard-tags/index.tsx | 31 ++++++++++---------- 6 files changed, 62 insertions(+), 42 deletions(-) diff --git a/app/components/ui/table.tsx b/app/components/ui/table.tsx index ab29799..3fb8d2f 100644 --- a/app/components/ui/table.tsx +++ b/app/components/ui/table.tsx @@ -1,11 +1,11 @@ import DT, { type Config, type ConfigColumns } from 'datatables.net-dt' -import DataTable from 'datatables.net-react' +import DataTable, { type DataTableSlots } from 'datatables.net-react' import React from 'react' type UiTableProperties = { - data: any // eslint-disable-line @typescript-eslint/no-explicit-any + data: any[] // eslint-disable-line @typescript-eslint/no-explicit-any columns: ConfigColumns[] - slots?: any // eslint-disable-line @typescript-eslint/no-explicit-any + slots?: DataTableSlots options?: Config title: string } diff --git a/app/layouts/admin/menu.ts b/app/layouts/admin/menu.ts index 69e18cb..b4c9315 100644 --- a/app/layouts/admin/menu.ts +++ b/app/layouts/admin/menu.ts @@ -72,4 +72,20 @@ export const SUB_MENU = [ title: 'Update Artikel', path: '/lg-admin/contents/update', }, + { + title: 'Buat Kategori', + path: '/lg-admin/categories/create', + }, + { + title: 'Update Kategori', + path: '/lg-admin/categories/update', + }, + { + title: 'Buat Tag', + path: '/lg-admin/tags/create', + }, + { + title: 'Update Tag', + path: '/lg-admin/tags/update', + }, ] diff --git a/app/pages/dashboard-advertisements/index.tsx b/app/pages/dashboard-advertisements/index.tsx index 6a596e6..95def65 100644 --- a/app/pages/dashboard-advertisements/index.tsx +++ b/app/pages/dashboard-advertisements/index.tsx @@ -1,5 +1,7 @@ import { Field, Input, Label, Select } from '@headlessui/react' import { MagnifyingGlassIcon } from '@heroicons/react/20/solid' +import type { ConfigColumns } from 'datatables.net-dt' +import type { DataTableSlots } from 'datatables.net-react' import { useState } from 'react' import { twMerge } from 'tailwind-merge' @@ -36,14 +38,14 @@ export const AdvertisementsPage = ({ } const dataBanner = BANNER - const dataColumns = [ + const dataColumns: ConfigColumns[] = [ { title: 'No', data: 'id' }, { title: 'Banner', data: 'urlImage' }, { title: 'Link', data: 'link' }, { title: 'Tgl Create', data: 'createdAt' }, { title: 'Status', data: 'status' }, ] - const dataSlot = { + const dataSlot: DataTableSlots = { 1: (value: string) => { return (
diff --git a/app/pages/dashboard-categories/index.tsx b/app/pages/dashboard-categories/index.tsx index 66056d2..5a67ed1 100644 --- a/app/pages/dashboard-categories/index.tsx +++ b/app/pages/dashboard-categories/index.tsx @@ -1,5 +1,5 @@ -import DT from 'datatables.net-dt' -import DataTable from 'datatables.net-react' +import DT, { type Config, type ConfigColumns } from 'datatables.net-dt' +import DataTable, { type DataTableSlots } from 'datatables.net-react' import { Link, useRouteLoaderData } from 'react-router' import type { TCategoryResponse } from '~/apis/common/get-categories' @@ -13,12 +13,13 @@ export const CategoriesPage = () => { ) DataTable.use(DT) - const dataTable = loaderData?.categoriesData?.sort((a, b) => { - if (a.sequence === null) return 1 - if (b.sequence === null) return -1 - return a.sequence - b.sequence - }) - const dataColumns = [ + const dataTable = + loaderData?.categoriesData?.sort((a, b) => { + if (a.sequence === null) return 1 + if (b.sequence === null) return -1 + return a.sequence - b.sequence + }) || [] + const dataColumns: ConfigColumns[] = [ { title: 'No', render: ( @@ -46,7 +47,7 @@ export const CategoriesPage = () => { data: 'id', }, ] - const dataSlot = { + const dataSlot: DataTableSlots = { 1: (_value: unknown, _type: unknown, data: TCategoryResponse) => (
{data.name}
@@ -64,7 +65,7 @@ export const CategoriesPage = () => { ), } - const dataOptions = { + const dataOptions: Config = { paging: true, searching: true, ordering: true, diff --git a/app/pages/dashboard-contents/index.tsx b/app/pages/dashboard-contents/index.tsx index 450b7d1..fb13630 100644 --- a/app/pages/dashboard-contents/index.tsx +++ b/app/pages/dashboard-contents/index.tsx @@ -1,5 +1,5 @@ -import DT from 'datatables.net-dt' -import DataTable from 'datatables.net-react' +import DT, { type Config, type ConfigColumns } from 'datatables.net-dt' +import DataTable, { type DataTableSlots } from 'datatables.net-react' import { Link, useRouteLoaderData } from 'react-router' import type { TCategoryResponse } from '~/apis/common/get-categories' @@ -17,16 +17,17 @@ export const ContentsPage = () => { ) DataTable.use(DT) - const dataTable = loaderData?.newsData?.sort( - (a, b) => new Date(b.live_at).getTime() - new Date(a.live_at).getTime(), - ) - const dataColumns = [ + const dataTable = + loaderData?.newsData?.sort( + (a, b) => new Date(b.live_at).getTime() - new Date(a.live_at).getTime(), + ) || [] + const dataColumns: ConfigColumns[] = [ { title: 'No', render: ( - data: unknown, - type: unknown, - row: unknown, + _data: unknown, + _type: unknown, + _row: unknown, meta: { row: number }, ) => { return meta.row + 1 @@ -54,7 +55,7 @@ export const ContentsPage = () => { data: 'slug', }, ] - const dataSlot = { + const dataSlot: DataTableSlots = { 1: (value: string) => formatDate(value), 2: (_value: unknown, _type: unknown, data: TNewsResponse) => (
@@ -62,6 +63,7 @@ export const ContentsPage = () => {
ID: {data.id.slice(0, 8)}
), + 3: (value: string) => {value}, 4: (value: TCategoryResponse[]) => (
{value.map((item) => item.name).join(', ')}
), @@ -89,7 +91,7 @@ export const ContentsPage = () => { ), } - const dataOptions = { + const dataOptions: Config = { paging: true, searching: true, ordering: true, diff --git a/app/pages/dashboard-tags/index.tsx b/app/pages/dashboard-tags/index.tsx index 8d6bddc..06225ed 100644 --- a/app/pages/dashboard-tags/index.tsx +++ b/app/pages/dashboard-tags/index.tsx @@ -1,5 +1,5 @@ -import DT from 'datatables.net-dt' -import DataTable from 'datatables.net-react' +import DT, { type Config, type ConfigColumns } from 'datatables.net-dt' +import DataTable, { type DataTableSlots } from 'datatables.net-react' import { Link, useRouteLoaderData } from 'react-router' import { Button } from '~/components/ui/button' @@ -14,13 +14,13 @@ export const TagsPage = () => { const { tagsData: dataTable } = loaderData || {} DataTable.use(DT) - const dataColumns = [ + const dataColumns: ConfigColumns[] = [ { title: 'No', render: ( - data: unknown, - type: unknown, - row: unknown, + _data: unknown, + _type: unknown, + _row: unknown, meta: { row: number }, ) => { return meta.row + 1 @@ -39,15 +39,7 @@ export const TagsPage = () => { data: 'id', }, ] - - const dataOptions = { - paging: true, - searching: true, - ordering: true, - info: true, - } - - const dataSlot = { + const dataSlot: DataTableSlots = { 3: (value: string) => ( ), } + const dataOptions: Config = { + paging: true, + searching: true, + ordering: true, + info: true, + } + return (
@@ -75,7 +74,7 @@ export const TagsPage = () => {
Date: Sun, 9 Mar 2025 15:34:00 +0800 Subject: [PATCH 6/6] refactor: rename parameters in CategoriesPage for clarity --- app/pages/dashboard-categories/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/pages/dashboard-categories/index.tsx b/app/pages/dashboard-categories/index.tsx index 5a67ed1..46e8c97 100644 --- a/app/pages/dashboard-categories/index.tsx +++ b/app/pages/dashboard-categories/index.tsx @@ -23,8 +23,8 @@ export const CategoriesPage = () => { { title: 'No', render: ( - data: unknown, - type: unknown, + _data: unknown, + _type: unknown, row: TCategoryResponse, meta: { row: number }, ) => {