Commit bbbfaaff authored by Gillian97's avatar Gillian97

feat: style opt & merge app settings

parent 84ef9d0c
...@@ -67,16 +67,14 @@ const CardView: FC<ICardViewProps> = ({ appId }) => { ...@@ -67,16 +67,14 @@ const CardView: FC<ICardViewProps> = ({ appId }) => {
} }
return ( return (
<div className='flex flex-row justify-between w-full mb-6'> <div className='min-w-max grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
<AppCard <AppCard
className='mr-3 flex-1'
appInfo={response} appInfo={response}
cardType='webapp' cardType='webapp'
onChangeStatus={onChangeSiteStatus} onChangeStatus={onChangeSiteStatus}
onGenerateCode={onGenerateCode} onGenerateCode={onGenerateCode}
onSaveSiteConfig={onSaveSiteConfig} /> onSaveSiteConfig={onSaveSiteConfig} />
<AppCard <AppCard
className='ml-3 flex-1'
cardType='api' cardType='api'
appInfo={response} appInfo={response}
onChangeStatus={onChangeApiStatus} /> onChangeStatus={onChangeApiStatus} />
......
...@@ -46,35 +46,23 @@ export default function ChartView({ appId }: IChartViewProps) { ...@@ -46,35 +46,23 @@ export default function ChartView({ appId }: IChartViewProps) {
defaultValue={7} defaultValue={7}
/> />
</div> </div>
<div className='flex flex-row w-full mb-6'> <div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
<div className='flex-1 mr-3'> <ConversationsChart period={period} id={appId} />
<ConversationsChart period={period} id={appId} /> <EndUsersChart period={period} id={appId} />
</div>
<div className='flex-1 ml-3'>
<EndUsersChart period={period} id={appId} />
</div>
</div> </div>
<div className='flex flex-row w-full mb-6'> <div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
<div className='flex-1 mr-3'> {isChatApp
{isChatApp ? (
? ( <AvgSessionInteractions period={period} id={appId} />
<AvgSessionInteractions period={period} id={appId} /> )
) : (
: ( <AvgResponseTime period={period} id={appId} />
<AvgResponseTime period={period} id={appId} /> )}
)} <TokenPerSecond period={period} id={appId} />
</div>
<div className='flex-1 ml-3'>
<TokenPerSecond period={period} id={appId} />
</div>
</div> </div>
<div className='flex flex-row w-full mb-6'> <div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
<div className='flex-1 ml-3'> <UserSatisfactionRate period={period} id={appId} />
<UserSatisfactionRate period={period} id={appId} /> <CostChart period={period} id={appId} />
</div>
<div className='flex-1 ml-3'>
<CostChart period={period} id={appId} />
</div>
</div> </div>
</div> </div>
) )
......
...@@ -2,22 +2,22 @@ ...@@ -2,22 +2,22 @@
import { useContext, useContextSelector } from 'use-context-selector' import { useContext, useContextSelector } from 'use-context-selector'
import Link from 'next/link' import Link from 'next/link'
import type { MouseEventHandler } from 'react'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import cn from 'classnames' import cn from 'classnames'
import style from '../list.module.css' import style from '../list.module.css'
import AppModeLabel from './AppModeLabel' import AppModeLabel from './AppModeLabel'
import AppSettings from './AppSettings'
import s from './style.module.css' import s from './style.module.css'
import SettingsModal from '@/app/components/app/overview/settings'
import type { App } from '@/types/app' import type { App } from '@/types/app'
import Confirm from '@/app/components/base/confirm' import Confirm from '@/app/components/base/confirm'
import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
import { deleteApp, updateAppIcon, updateAppName } from '@/service/apps' import { deleteApp, fetchAppDetail, updateAppSiteConfig } from '@/service/apps'
import AppIcon from '@/app/components/base/app-icon' import AppIcon from '@/app/components/base/app-icon'
import AppsContext from '@/context/app-context' import AppsContext from '@/context/app-context'
import CustomPopover from '@/app/components/base/popover' import CustomPopover from '@/app/components/base/popover'
import Divider from '@/app/components/base/divider' import Divider from '@/app/components/base/divider'
import { asyncRunSafe } from '@/utils'
export type AppCardProps = { export type AppCardProps = {
app: App app: App
...@@ -35,17 +35,10 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { ...@@ -35,17 +35,10 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
const [showConfirmDelete, setShowConfirmDelete] = useState(false) const [showConfirmDelete, setShowConfirmDelete] = useState(false)
const [showSettingsModal, setShowSettingsModal] = useState(false) const [showSettingsModal, setShowSettingsModal] = useState(false)
const [updateLoading, setUpdateLoading] = useState(false) const [detailState, setDetailState] = useState<{
loading: boolean
const onClickSettings: MouseEventHandler = useCallback((e) => { detail?: App
e.preventDefault() }>({ loading: false })
setShowSettingsModal(true)
}, [])
const onClickDelete: MouseEventHandler = useCallback((e) => {
e.preventDefault()
setShowConfirmDelete(true)
}, [])
const onConfirmDelete = useCallback(async () => { const onConfirmDelete = useCallback(async () => {
try { try {
...@@ -66,34 +59,34 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { ...@@ -66,34 +59,34 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
setShowConfirmDelete(false) setShowConfirmDelete(false)
}, [app.id]) }, [app.id])
const onUpdate = useCallback( const getAppDetail = async () => {
async (params: { name: App['name']; iconInfo: { icon: string;icon_background: string } }) => { setDetailState({ loading: true })
const reqList = [] const [err, res] = await asyncRunSafe<App>(
if (params.name !== app.name) { fetchAppDetail({ url: '/apps', id: app.id }) as Promise<App>,
reqList.push(updateAppName({ )
url: `/apps/${app.id}/name`, if (!err) {
body: { name: params.name }, setDetailState({ loading: false, detail: res })
})) setShowSettingsModal(true)
} }
if (params.iconInfo.icon !== app.icon || params.iconInfo.icon_background !== app.icon_background) else { setDetailState({ loading: false }) }
reqList.push(updateAppIcon({ url: `/apps/${app.id}/icon`, body: params.iconInfo })) }
if (!reqList.length) { const onSaveSiteConfig = useCallback(
setShowSettingsModal(false) async (params: any) => {
notify({ const [err] = await asyncRunSafe<App>(
type: 'info', updateAppSiteConfig({
message: t('common.actionMsg.noModification'), url: `/apps/${app.id}/site`,
}) body: params,
return }) as Promise<App>,
} )
setUpdateLoading(true) if (!err) {
const updateRes = await Promise.allSettled(reqList)
if (!updateRes.find(v => v.status === 'rejected')) {
notify({ notify({
type: 'success', type: 'success',
message: t('common.actionMsg.modifiedSuccessfully'), message: t('common.actionMsg.modifiedSuccessfully'),
}) })
setShowSettingsModal(false) if (onRefresh)
onRefresh()
mutateApps()
} }
else { else {
notify({ notify({
...@@ -101,44 +94,45 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { ...@@ -101,44 +94,45 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
message: t('common.actionMsg.modificationFailed'), message: t('common.actionMsg.modificationFailed'),
}) })
} }
if (onRefresh)
onRefresh()
mutateApps()
setUpdateLoading(false)
}, },
[app.id], [app.id],
) )
const Operations = (props: any) => { const Operations = (props: any) => {
return <div className="w-full py-1"> const onClickSettings = async (e: any) => {
<div className={s.actionItem} onClick={(e) => { props?.onClose()
props?.onClose() e.preventDefault()
onClickSettings(e) await getAppDetail()
}}> }
<span className={s.actionName}> const onClickDelete = async (e: any) => {
{t('common.operation.settings')} props?.onClose()
</span> e.preventDefault()
</div> setShowConfirmDelete(true)
<Divider className="my-1" /> }
<div return (
className={cn(s.actionItem, s.deleteActionItem, 'group')} <div className="w-full py-1">
onClick={(e) => { <button className={s.actionItem} onClick={onClickSettings} disabled={detailState.loading}>
props?.onClose() <span className={s.actionName}>{t('common.operation.settings')}</span>
onClickDelete(e) </button>
}} <Divider className="!my-1" />
> <div
<span className={cn(s.actionItem, s.deleteActionItem, 'group')}
className={cn(s.actionName, 'group-hover:text-red-500')} onClick={onClickDelete}
> >
{t('common.operation.delete')} <span className={cn(s.actionName, 'group-hover:text-red-500')}>
</span> {t('common.operation.delete')}
</span>
</div>
</div> </div>
</div> )
} }
return ( return (
<> <>
<Link href={`/app/${app.id}/overview`} className={style.listItem}> <Link
href={`/app/${app.id}/overview`}
className={style.listItem}
>
<div className={style.listItemTitle}> <div className={style.listItemTitle}>
<AppIcon <AppIcon
size="small" size="small"
...@@ -149,10 +143,9 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { ...@@ -149,10 +143,9 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
<div className={style.listItemHeadingContent}>{app.name}</div> <div className={style.listItemHeadingContent}>{app.name}</div>
</div> </div>
<CustomPopover <CustomPopover
htmlContent={ htmlContent={<Operations />}
<Operations />
}
position="br" position="br"
trigger="click"
btnElement={<div className={cn(s.actionIcon, s.commonIcon)} />} btnElement={<div className={cn(s.actionIcon, s.commonIcon)} />}
btnClassName={open => btnClassName={open =>
cn( cn(
...@@ -180,14 +173,12 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { ...@@ -180,14 +173,12 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
onCancel={() => setShowConfirmDelete(false)} onCancel={() => setShowConfirmDelete(false)}
/> />
)} )}
{showSettingsModal && ( {showSettingsModal && detailState.detail && (
<AppSettings <SettingsModal
appInfo={detailState.detail}
isShow={showSettingsModal} isShow={showSettingsModal}
onClose={() => setShowSettingsModal(false)} onClose={() => setShowSettingsModal(false)}
appName={app.name} onSave={onSaveSiteConfig}
appIcon={{ icon: app.icon, icon_background: app.icon_background }}
onUpdate={onUpdate}
loading={updateLoading}
/> />
)} )}
</Link> </Link>
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
} }
.actionItem { .actionItem {
@apply h-9 py-2 px-3 mx-1 flex items-center gap-2 hover:bg-gray-100 rounded-lg cursor-pointer; @apply h-9 py-2 px-3 mx-1 flex items-center gap-2 hover:bg-gray-100 rounded-lg cursor-pointer;
width: calc(100% - 0.5rem);
} }
.deleteActionItem { .deleteActionItem {
@apply hover:bg-red-50 !important; @apply hover:bg-red-50 !important;
......
...@@ -134,11 +134,11 @@ function AppCard({ ...@@ -134,11 +134,11 @@ function AppCard({
return ( return (
<div <div
className={`flex flex-col w-full shadow-sm border-[0.5px] rounded-lg border-gray-200 ${ className={`min-w-max shadow-sm border-[0.5px] rounded-lg border-gray-200 ${
className ?? '' className ?? ''
}`} }`}
> >
<div className={`px-6 py-4 ${customBgColor ?? bgColor} rounded-lg`}> <div className={`px-6 py-5 ${customBgColor ?? bgColor} rounded-lg`}>
<div className="mb-2.5 flex flex-row items-start justify-between"> <div className="mb-2.5 flex flex-row items-start justify-between">
<AppBasic <AppBasic
iconType={cardType} iconType={cardType}
...@@ -208,7 +208,7 @@ function AppCard({ ...@@ -208,7 +208,7 @@ function AppCard({
: !runningStatus : !runningStatus
return ( return (
<Button <Button
className="mr-2 border-[0.5px] hover:outline hover:outline-[0.5px] hover:outline-gray-300 text-gray-700 font-medium bg-white shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)]" className="mr-2 border-[0.5px] !h-8 hover:outline hover:outline-[0.5px] hover:outline-gray-300 text-gray-700 font-medium bg-white shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)]"
key={op.opName} key={op.opName}
onClick={genClickFuncByName(op.opName)} onClick={genClickFuncByName(op.opName)}
disabled={disabled} disabled={disabled}
......
...@@ -229,7 +229,7 @@ const Chart: React.FC<IChartProps> = ({ ...@@ -229,7 +229,7 @@ const Chart: React.FC<IChartProps> = ({
<div className='mb-3'> <div className='mb-3'>
<Basic name={title} type={timePeriod} hoverTip={explanation} /> <Basic name={title} type={timePeriod} hoverTip={explanation} />
</div> </div>
<div className='mb-4'> <div className='mb-4 flex-1'>
<Basic <Basic
isExtraInLine={CHART_TYPE_CONFIG[chartType].showTokens} isExtraInLine={CHART_TYPE_CONFIG[chartType].showTokens}
name={chartType !== 'costs' ? (sumData.toLocaleString() + unit) : `${sumData < 1000 ? sumData : (`${formatNumber(Math.round(sumData / 1000))}k`)}`} name={chartType !== 'costs' ? (sumData.toLocaleString() + unit) : `${sumData < 1000 ? sumData : (`${formatNumber(Math.round(sumData / 1000))}k`)}`}
...@@ -347,6 +347,7 @@ export const UserSatisfactionRate: FC<IBizChartProps> = ({ id, period }) => { ...@@ -347,6 +347,7 @@ export const UserSatisfactionRate: FC<IBizChartProps> = ({ id, period }) => {
chartType='endUsers' chartType='endUsers'
isAvg isAvg
{...(noDataFlag && { yMax: 1000 })} {...(noDataFlag && { yMax: 1000 })}
className='h-full'
/> />
} }
......
...@@ -18,7 +18,7 @@ export type ISettingsModalProps = { ...@@ -18,7 +18,7 @@ export type ISettingsModalProps = {
isShow: boolean isShow: boolean
defaultValue?: string defaultValue?: string
onClose: () => void onClose: () => void
onSave: (params: ConfigParams) => Promise<any> onSave?: (params: ConfigParams) => Promise<any>
} }
export type ConfigParams = { export type ConfigParams = {
...@@ -70,7 +70,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({ ...@@ -70,7 +70,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
icon: emoji.icon, icon: emoji.icon,
icon_background: emoji.icon_background, icon_background: emoji.icon_background,
} }
await onSave(params) await onSave?.(params)
setSaveLoading(false) setSaveLoading(false)
onHide() onHide()
} }
...@@ -99,7 +99,9 @@ const SettingsModal: FC<ISettingsModalProps> = ({ ...@@ -99,7 +99,9 @@ const SettingsModal: FC<ISettingsModalProps> = ({
/> />
<input className={`flex-grow rounded-lg h-10 box-border px-3 ${s.projectName} bg-gray-100`} <input className={`flex-grow rounded-lg h-10 box-border px-3 ${s.projectName} bg-gray-100`}
value={inputInfo.title} value={inputInfo.title}
onChange={onChange('title')} /> onChange={onChange('title')}
placeholder={t('app.appNamePlaceholder') || ''}
/>
</div> </div>
<div className={`mt-6 font-medium ${s.settingTitle} text-gray-900 `}>{t(`${prefixSettings}.webDesc`)}</div> <div className={`mt-6 font-medium ${s.settingTitle} text-gray-900 `}>{t(`${prefixSettings}.webDesc`)}</div>
<p className={`mt-1 ${s.settingsTip} text-gray-500`}>{t(`${prefixSettings}.webDescTip`)}</p> <p className={`mt-1 ${s.settingsTip} text-gray-500`}>{t(`${prefixSettings}.webDescTip`)}</p>
......
...@@ -64,7 +64,7 @@ const translation = { ...@@ -64,7 +64,7 @@ const translation = {
apiInfo: { apiInfo: {
title: 'Backend service API', title: 'Backend service API',
explanation: 'Easily integrated into your application', explanation: 'Easily integrated into your application',
accessibleAddress: 'API Token', accessibleAddress: 'Service API Endpoint',
doc: 'API Reference', doc: 'API Reference',
}, },
status: { status: {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment