diff --git a/frontend/src/components/ChatListDrawer.tsx b/frontend/src/components/ChatListDrawer.tsx index 648d6283e..bceb2465f 100644 --- a/frontend/src/components/ChatListDrawer.tsx +++ b/frontend/src/components/ChatListDrawer.tsx @@ -326,7 +326,7 @@ const ChatListDrawer: React.FC = (props) => { } - to="admin/published-bot-apis" + to="admin/publish-apis" labelComponent={t('button.botPublishApis')} /> { return http.get(['/admin/published-bots', req]); }, listPublicBots: (req: ListPublicBotsRequest) => { - return http.get(['/admin/public-bots', req]); + return http.get( + !!req.start === !!req.end ? ['/admin/public-bots', req] : null + ); }, }; }; diff --git a/frontend/src/i18n/en/index.ts b/frontend/src/i18n/en/index.ts index fdcb1197c..73f133360 100644 --- a/frontend/src/i18n/en/index.ts +++ b/frontend/src/i18n/en/index.ts @@ -234,7 +234,7 @@ How would you categorize this email?`, pageTitle: 'Public Bot Usages', noPublicBotUsages: 'During the Calculation Period, the public bots were not utilized.', - published: 'The Bot API is published.', + published: 'API is published.', SearchCondition: { title: 'Calculation Period', from: 'From', @@ -245,8 +245,18 @@ How would you categorize this email?`, help: { overview: 'Monitor the usage status of Shared Bots and Published Bot APIs.', + calculationPeriod: + 'If the Calculation Period is not set, the cost for today will be displayed.', }, }, + publishApis: { + label: { + pageTitle: 'Bot Publish APIs', + }, + }, + validationError: { + period: 'Enter both From and To', + }, }, deleteDialog: { title: 'Delete?', diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index e2d502b91..e25e5cc50 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -10,6 +10,7 @@ import BotExplorePage from './pages/BotExplorePage.tsx'; import BotEditPage from './pages/BotEditPage.tsx'; import BotApiSettingsPage from './pages/BotApiSettingsPage.tsx'; import AdminPublicBotsPage from './pages/AdminPublicBotsPage.tsx'; +import AdminBotApisPage from './pages/AdminBotApisPage.tsx'; const router = createBrowserRouter([ { @@ -44,6 +45,10 @@ const router = createBrowserRouter([ path: '/admin/public-bots', element: , }, + { + path: '/admin/publish-apis', + element: , + }, { path: '/:conversationId', element: , diff --git a/frontend/src/pages/AdminBotApisPage.tsx b/frontend/src/pages/AdminBotApisPage.tsx new file mode 100644 index 000000000..b96ecd8d4 --- /dev/null +++ b/frontend/src/pages/AdminBotApisPage.tsx @@ -0,0 +1,154 @@ +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import Help from '../components/Help'; +import usePublicBotsForAdmin from '../hooks/usePublicBotsForAdmin'; +import ListItemBot from '../components/ListItemBot'; +import { formatDate, formatDatetime } from '../utils/DateUtils'; + +import InputText from '../components/InputText'; +import Button from '../components/Button'; +import { PiArrowDown } from 'react-icons/pi'; +import Skeleton from '../components/Skeleton'; + +const AdminBotApisPage: React.FC = () => { + const { t } = useTranslation(); + // const navigate = useNavigate(); + + const [searchDateFrom, setSearchDateFrom] = useState(null); + const [searchDateTo, setSearchDateTo] = useState(null); + + const { publicBots, isLoading: isLoadingPublicBots } = usePublicBotsForAdmin({ + start: searchDateFrom ? searchDateFrom + '00' : undefined, + end: searchDateTo ? searchDateTo + '23' : undefined, + }); + + // const onClickViewBot = useCallback( + // (botId: string) => { + // navigate(`/bot/edit/${botId}`); + // }, + // [navigate] + // ); + + return ( + <> + {/* { + setIsOpenDeleteDialog(false); + }} + /> + { + setIsOpenShareDialog(false); + }} + /> */} +
+
+
+
+
+
+ {t('admin.publishApis.label.pageTitle')} +
+ +
+
+ +
+
+ {t('admin.publicBotUsages.label.SearchCondition.title')} +
+ +
+ { + setSearchDateFrom(formatDate(val, 'YYYYMMDD')); + }} + /> + { + setSearchDateTo(formatDate(val, 'YYYYMMDD')); + }} + /> +
+
+ +
+ +
+ +
+ +
+ {isLoadingPublicBots && ( +
+ {new Array(15).fill('').map((_, idx) => { + return ; + })} +
+ )} + + {publicBots?.length === 0 && ( +
+ {t('admin.publicBotUsages.label.noPublicBotUsages')} +
+ )} + {publicBots?.map((bot, idx) => ( + { + // onClickViewBot(bot.id); + }}> +
+
+ {(Math.floor(bot.totalPrice * 100) / 100).toFixed(2)} USD +
+ +
+ {bot.isPublished ? ( + <> +
+ {/* {t('admin.label.publishedDate')}: */} +
+ {bot.publishedDatetime + ? formatDatetime(bot.publishedDatetime) + : null} + + ) : ( +
+ )} +
+
+
+ ))} +
+
+
+
+ + ); +}; + +export default AdminBotApisPage; diff --git a/frontend/src/pages/AdminPublicBotsPage.tsx b/frontend/src/pages/AdminPublicBotsPage.tsx index 789eefb52..abfe9619b 100644 --- a/frontend/src/pages/AdminPublicBotsPage.tsx +++ b/frontend/src/pages/AdminPublicBotsPage.tsx @@ -1,52 +1,49 @@ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import Help from '../components/Help'; import usePublicBotsForAdmin from '../hooks/usePublicBotsForAdmin'; import ListItemBot from '../components/ListItemBot'; -import { formatDate, formatDatetime } from '../utils/DateUtils'; +import { addDate, formatDate } from '../utils/DateUtils'; import InputText from '../components/InputText'; import Button from '../components/Button'; import { PiArrowDown } from 'react-icons/pi'; import Skeleton from '../components/Skeleton'; +import { twMerge } from 'tailwind-merge'; + +const DATA_FORMAT = 'YYYYMMDD'; const AdminPublicBotsPage: React.FC = () => { const { t } = useTranslation(); - // const navigate = useNavigate(); - const [searchDateFrom, setSearchDateFrom] = useState(null); - const [searchDateTo, setSearchDateTo] = useState(null); + const [searchDateFrom, setSearchDateFrom] = useState( + formatDate(addDate(new Date(), -1, 'month'), DATA_FORMAT) + ); + const [searchDateTo, setSearchDateTo] = useState( + formatDate(new Date(), DATA_FORMAT) + ); + const [isDescCost, setIsDescCost] = useState(true); const { publicBots, isLoading: isLoadingPublicBots } = usePublicBotsForAdmin({ - start: searchDateFrom ?? undefined, - end: searchDateTo ?? undefined, + start: searchDateFrom ? searchDateFrom + '00' : undefined, + end: searchDateTo ? searchDateTo + '23' : undefined, }); - // const onClickViewBot = useCallback( - // (botId: string) => { - // navigate(`/bot/edit/${botId}`); - // }, - // [navigate] - // ); + const sortedBots = useMemo(() => { + const tmp = isDescCost ? -1 : 1; + return publicBots?.sort((a, b) => + a.totalPrice > b.totalPrice ? tmp : tmp * -1 + ); + }, [isDescCost, publicBots]); + + const validationErrorMessage = useMemo(() => { + return !!searchDateFrom === !!searchDateTo + ? null + : t('admin.validationError.period'); + }, [searchDateFrom, searchDateTo, t]); return ( <> - {/* { - setIsOpenDeleteDialog(false); - }} - /> - { - setIsOpenShareDialog(false); - }} - /> */}
@@ -63,8 +60,11 @@ const AdminPublicBotsPage: React.FC = () => {
-
+
{t('admin.publicBotUsages.label.SearchCondition.title')} +
@@ -74,8 +74,17 @@ const AdminPublicBotsPage: React.FC = () => { label={t('admin.publicBotUsages.label.SearchCondition.from')} value={formatDate(searchDateFrom, 'YYYY-MM-DD')} onChange={(val) => { - setSearchDateFrom(formatDate(val, 'YYYYMMDD')); + if (val === '') { + setSearchDateFrom(null); + return; + } + setSearchDateFrom(formatDate(val, DATA_FORMAT)); }} + errorMessage={ + searchDateFrom + ? undefined + : validationErrorMessage ?? undefined + } /> { label={t('admin.publicBotUsages.label.SearchCondition.to')} value={formatDate(searchDateTo, 'YYYY-MM-DD')} onChange={(val) => { - setSearchDateTo(formatDate(val, 'YYYYMMDD')); + if (val === '') { + setSearchDateTo(null); + return; + } + setSearchDateTo(formatDate(val, DATA_FORMAT)); }} + errorMessage={ + searchDateTo + ? undefined + : validationErrorMessage ?? undefined + } />
-
@@ -111,7 +141,7 @@ const AdminPublicBotsPage: React.FC = () => { {t('admin.publicBotUsages.label.noPublicBotUsages')}
)} - {publicBots?.map((bot, idx) => ( + {sortedBots?.map((bot, idx) => ( { onClick={() => { // onClickViewBot(bot.id); }}> -
-
+
+
{(Math.floor(bot.totalPrice * 100) / 100).toFixed(2)} USD
-
+
{bot.isPublished ? ( <> -
- {/* {t('admin.label.publishedDate')}: */} -
- {bot.publishedDatetime - ? formatDatetime(bot.publishedDatetime) + {bot.isPublished + ? t('admin.publicBotUsages.label.published') : null} ) : ( diff --git a/frontend/src/utils/DateUtils.ts b/frontend/src/utils/DateUtils.ts index 38d12628f..5e5f32837 100644 --- a/frontend/src/utils/DateUtils.ts +++ b/frontend/src/utils/DateUtils.ts @@ -1,4 +1,4 @@ -import dayjs from 'dayjs'; +import dayjs, { ManipulateType } from 'dayjs'; const DEFAULT_DATETIME_FORMAT = 'YYYY/MM/DD HH:mm:ss'; const DEFAULT_DATE_FORMAT = 'YYYY/MM/DD'; @@ -10,3 +10,11 @@ export const formatDatetime = (date: dayjs.ConfigType, format?: string) => { export const formatDate = (date: dayjs.ConfigType, format?: string) => { return dayjs(date).format(format ?? DEFAULT_DATE_FORMAT); }; + +export const addDate = ( + date: dayjs.ConfigType, + value: number, + unit?: ManipulateType +) => { + return dayjs(date).add(value, unit).toDate(); +};