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 }) => {
}
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
className='mr-3 flex-1'
appInfo={response}
cardType='webapp'
onChangeStatus={onChangeSiteStatus}
onGenerateCode={onGenerateCode}
onSaveSiteConfig={onSaveSiteConfig} />
<AppCard
className='ml-3 flex-1'
cardType='api'
appInfo={response}
onChangeStatus={onChangeApiStatus} />
......
......@@ -46,16 +46,11 @@ export default function ChartView({ appId }: IChartViewProps) {
defaultValue={7}
/>
</div>
<div className='flex flex-row w-full mb-6'>
<div className='flex-1 mr-3'>
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
<ConversationsChart period={period} id={appId} />
</div>
<div className='flex-1 ml-3'>
<EndUsersChart period={period} id={appId} />
</div>
</div>
<div className='flex flex-row w-full mb-6'>
<div className='flex-1 mr-3'>
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
{isChatApp
? (
<AvgSessionInteractions period={period} id={appId} />
......@@ -63,19 +58,12 @@ export default function ChartView({ appId }: IChartViewProps) {
: (
<AvgResponseTime period={period} id={appId} />
)}
</div>
<div className='flex-1 ml-3'>
<TokenPerSecond period={period} id={appId} />
</div>
</div>
<div className='flex flex-row w-full mb-6'>
<div className='flex-1 ml-3'>
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
<UserSatisfactionRate period={period} id={appId} />
</div>
<div className='flex-1 ml-3'>
<CostChart period={period} id={appId} />
</div>
</div>
</div>
)
}
......@@ -2,22 +2,22 @@
import { useContext, useContextSelector } from 'use-context-selector'
import Link from 'next/link'
import type { MouseEventHandler } from 'react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import style from '../list.module.css'
import AppModeLabel from './AppModeLabel'
import AppSettings from './AppSettings'
import s from './style.module.css'
import SettingsModal from '@/app/components/app/overview/settings'
import type { App } from '@/types/app'
import Confirm from '@/app/components/base/confirm'
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 AppsContext from '@/context/app-context'
import CustomPopover from '@/app/components/base/popover'
import Divider from '@/app/components/base/divider'
import { asyncRunSafe } from '@/utils'
export type AppCardProps = {
app: App
......@@ -35,17 +35,10 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
const [showSettingsModal, setShowSettingsModal] = useState(false)
const [updateLoading, setUpdateLoading] = useState(false)
const onClickSettings: MouseEventHandler = useCallback((e) => {
e.preventDefault()
setShowSettingsModal(true)
}, [])
const onClickDelete: MouseEventHandler = useCallback((e) => {
e.preventDefault()
setShowConfirmDelete(true)
}, [])
const [detailState, setDetailState] = useState<{
loading: boolean
detail?: App
}>({ loading: false })
const onConfirmDelete = useCallback(async () => {
try {
......@@ -66,34 +59,34 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
setShowConfirmDelete(false)
}, [app.id])
const onUpdate = useCallback(
async (params: { name: App['name']; iconInfo: { icon: string;icon_background: string } }) => {
const reqList = []
if (params.name !== app.name) {
reqList.push(updateAppName({
url: `/apps/${app.id}/name`,
body: { name: params.name },
}))
const getAppDetail = async () => {
setDetailState({ loading: true })
const [err, res] = await asyncRunSafe<App>(
fetchAppDetail({ url: '/apps', id: app.id }) as Promise<App>,
)
if (!err) {
setDetailState({ loading: false, detail: res })
setShowSettingsModal(true)
}
if (params.iconInfo.icon !== app.icon || params.iconInfo.icon_background !== app.icon_background)
reqList.push(updateAppIcon({ url: `/apps/${app.id}/icon`, body: params.iconInfo }))
if (!reqList.length) {
setShowSettingsModal(false)
notify({
type: 'info',
message: t('common.actionMsg.noModification'),
})
return
else { setDetailState({ loading: false }) }
}
setUpdateLoading(true)
const updateRes = await Promise.allSettled(reqList)
if (!updateRes.find(v => v.status === 'rejected')) {
const onSaveSiteConfig = useCallback(
async (params: any) => {
const [err] = await asyncRunSafe<App>(
updateAppSiteConfig({
url: `/apps/${app.id}/site`,
body: params,
}) as Promise<App>,
)
if (!err) {
notify({
type: 'success',
message: t('common.actionMsg.modifiedSuccessfully'),
})
setShowSettingsModal(false)
if (onRefresh)
onRefresh()
mutateApps()
}
else {
notify({
......@@ -101,44 +94,45 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
message: t('common.actionMsg.modificationFailed'),
})
}
if (onRefresh)
onRefresh()
mutateApps()
setUpdateLoading(false)
},
[app.id],
)
const Operations = (props: any) => {
return <div className="w-full py-1">
<div className={s.actionItem} onClick={(e) => {
const onClickSettings = async (e: any) => {
props?.onClose()
onClickSettings(e)
}}>
<span className={s.actionName}>
{t('common.operation.settings')}
</span>
</div>
<Divider className="my-1" />
e.preventDefault()
await getAppDetail()
}
const onClickDelete = async (e: any) => {
props?.onClose()
e.preventDefault()
setShowConfirmDelete(true)
}
return (
<div className="w-full py-1">
<button className={s.actionItem} onClick={onClickSettings} disabled={detailState.loading}>
<span className={s.actionName}>{t('common.operation.settings')}</span>
</button>
<Divider className="!my-1" />
<div
className={cn(s.actionItem, s.deleteActionItem, 'group')}
onClick={(e) => {
props?.onClose()
onClickDelete(e)
}}
>
<span
className={cn(s.actionName, 'group-hover:text-red-500')}
onClick={onClickDelete}
>
<span className={cn(s.actionName, 'group-hover:text-red-500')}>
{t('common.operation.delete')}
</span>
</div>
</div>
)
}
return (
<>
<Link href={`/app/${app.id}/overview`} className={style.listItem}>
<Link
href={`/app/${app.id}/overview`}
className={style.listItem}
>
<div className={style.listItemTitle}>
<AppIcon
size="small"
......@@ -149,10 +143,9 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
<div className={style.listItemHeadingContent}>{app.name}</div>
</div>
<CustomPopover
htmlContent={
<Operations />
}
htmlContent={<Operations />}
position="br"
trigger="click"
btnElement={<div className={cn(s.actionIcon, s.commonIcon)} />}
btnClassName={open =>
cn(
......@@ -180,14 +173,12 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
onCancel={() => setShowConfirmDelete(false)}
/>
)}
{showSettingsModal && (
<AppSettings
{showSettingsModal && detailState.detail && (
<SettingsModal
appInfo={detailState.detail}
isShow={showSettingsModal}
onClose={() => setShowSettingsModal(false)}
appName={app.name}
appIcon={{ icon: app.icon, icon_background: app.icon_background }}
onUpdate={onUpdate}
loading={updateLoading}
onSave={onSaveSiteConfig}
/>
)}
</Link>
......
......@@ -11,6 +11,7 @@
}
.actionItem {
@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 {
@apply hover:bg-red-50 !important;
......
......@@ -134,11 +134,11 @@ function AppCard({
return (
<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 ?? ''
}`}
>
<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">
<AppBasic
iconType={cardType}
......@@ -208,7 +208,7 @@ function AppCard({
: !runningStatus
return (
<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}
onClick={genClickFuncByName(op.opName)}
disabled={disabled}
......
......@@ -229,7 +229,7 @@ const Chart: React.FC<IChartProps> = ({
<div className='mb-3'>
<Basic name={title} type={timePeriod} hoverTip={explanation} />
</div>
<div className='mb-4'>
<div className='mb-4 flex-1'>
<Basic
isExtraInLine={CHART_TYPE_CONFIG[chartType].showTokens}
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 }) => {
chartType='endUsers'
isAvg
{...(noDataFlag && { yMax: 1000 })}
className='h-full'
/>
}
......
......@@ -18,7 +18,7 @@ export type ISettingsModalProps = {
isShow: boolean
defaultValue?: string
onClose: () => void
onSave: (params: ConfigParams) => Promise<any>
onSave?: (params: ConfigParams) => Promise<any>
}
export type ConfigParams = {
......@@ -70,7 +70,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
icon: emoji.icon,
icon_background: emoji.icon_background,
}
await onSave(params)
await onSave?.(params)
setSaveLoading(false)
onHide()
}
......@@ -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`}
value={inputInfo.title}
onChange={onChange('title')} />
onChange={onChange('title')}
placeholder={t('app.appNamePlaceholder') || ''}
/>
</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>
......
......@@ -64,7 +64,7 @@ const translation = {
apiInfo: {
title: 'Backend service API',
explanation: 'Easily integrated into your application',
accessibleAddress: 'API Token',
accessibleAddress: 'Service API Endpoint',
doc: 'API Reference',
},
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