feat: survey detail and edit

This commit is contained in:
moonrailgun 2024-05-02 01:02:13 +08:00
parent 12cd54eafe
commit a596011960
5 changed files with 166 additions and 19 deletions

View File

@ -243,7 +243,7 @@ export const SurveyEditForm: React.FC<SurveyEditFormProps> = React.memo(
<CardFooter> <CardFooter>
<Button type="submit" loading={isLoading}> <Button type="submit" loading={isLoading}>
{t('Create')} {props.defaultValues ? t('Update') : t('Create')}
</Button> </Button>
</CardFooter> </CardFooter>
</Card> </Card>

View File

@ -27,7 +27,6 @@ import { Route as WebsiteAddImport } from './routes/website/add'
import { Route as TelemetryAddImport } from './routes/telemetry/add' import { Route as TelemetryAddImport } from './routes/telemetry/add'
import { Route as TelemetryTelemetryIdImport } from './routes/telemetry/$telemetryId' import { Route as TelemetryTelemetryIdImport } from './routes/telemetry/$telemetryId'
import { Route as SurveyAddImport } from './routes/survey/add' import { Route as SurveyAddImport } from './routes/survey/add'
import { Route as SurveySurveyIdImport } from './routes/survey/$surveyId'
import { Route as StatusSlugImport } from './routes/status/$slug' import { Route as StatusSlugImport } from './routes/status/$slug'
import { Route as SettingsUsageImport } from './routes/settings/usage' import { Route as SettingsUsageImport } from './routes/settings/usage'
import { Route as SettingsProfileImport } from './routes/settings/profile' import { Route as SettingsProfileImport } from './routes/settings/profile'
@ -37,8 +36,10 @@ import { Route as PageAddImport } from './routes/page/add'
import { Route as PageSlugImport } from './routes/page/$slug' import { Route as PageSlugImport } from './routes/page/$slug'
import { Route as MonitorAddImport } from './routes/monitor/add' import { Route as MonitorAddImport } from './routes/monitor/add'
import { Route as WebsiteWebsiteIdIndexImport } from './routes/website/$websiteId/index' import { Route as WebsiteWebsiteIdIndexImport } from './routes/website/$websiteId/index'
import { Route as SurveySurveyIdIndexImport } from './routes/survey/$surveyId/index'
import { Route as MonitorMonitorIdIndexImport } from './routes/monitor/$monitorId/index' import { Route as MonitorMonitorIdIndexImport } from './routes/monitor/$monitorId/index'
import { Route as WebsiteWebsiteIdConfigImport } from './routes/website/$websiteId/config' import { Route as WebsiteWebsiteIdConfigImport } from './routes/website/$websiteId/config'
import { Route as SurveySurveyIdEditImport } from './routes/survey/$surveyId/edit'
import { Route as MonitorMonitorIdEditImport } from './routes/monitor/$monitorId/edit' import { Route as MonitorMonitorIdEditImport } from './routes/monitor/$monitorId/edit'
// Create/Update Routes // Create/Update Routes
@ -123,11 +124,6 @@ const SurveyAddRoute = SurveyAddImport.update({
getParentRoute: () => SurveyRoute, getParentRoute: () => SurveyRoute,
} as any) } as any)
const SurveySurveyIdRoute = SurveySurveyIdImport.update({
path: '/$surveyId',
getParentRoute: () => SurveyRoute,
} as any)
const StatusSlugRoute = StatusSlugImport.update({ const StatusSlugRoute = StatusSlugImport.update({
path: '/status/$slug', path: '/status/$slug',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
@ -173,6 +169,11 @@ const WebsiteWebsiteIdIndexRoute = WebsiteWebsiteIdIndexImport.update({
getParentRoute: () => WebsiteRoute, getParentRoute: () => WebsiteRoute,
} as any) } as any)
const SurveySurveyIdIndexRoute = SurveySurveyIdIndexImport.update({
path: '/$surveyId/',
getParentRoute: () => SurveyRoute,
} as any)
const MonitorMonitorIdIndexRoute = MonitorMonitorIdIndexImport.update({ const MonitorMonitorIdIndexRoute = MonitorMonitorIdIndexImport.update({
path: '/$monitorId/', path: '/$monitorId/',
getParentRoute: () => MonitorRoute, getParentRoute: () => MonitorRoute,
@ -183,6 +184,11 @@ const WebsiteWebsiteIdConfigRoute = WebsiteWebsiteIdConfigImport.update({
getParentRoute: () => WebsiteRoute, getParentRoute: () => WebsiteRoute,
} as any) } as any)
const SurveySurveyIdEditRoute = SurveySurveyIdEditImport.update({
path: '/$surveyId/edit',
getParentRoute: () => SurveyRoute,
} as any)
const MonitorMonitorIdEditRoute = MonitorMonitorIdEditImport.update({ const MonitorMonitorIdEditRoute = MonitorMonitorIdEditImport.update({
path: '/$monitorId/edit', path: '/$monitorId/edit',
getParentRoute: () => MonitorRoute, getParentRoute: () => MonitorRoute,
@ -268,10 +274,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof StatusSlugImport preLoaderRoute: typeof StatusSlugImport
parentRoute: typeof rootRoute parentRoute: typeof rootRoute
} }
'/survey/$surveyId': {
preLoaderRoute: typeof SurveySurveyIdImport
parentRoute: typeof SurveyImport
}
'/survey/add': { '/survey/add': {
preLoaderRoute: typeof SurveyAddImport preLoaderRoute: typeof SurveyAddImport
parentRoute: typeof SurveyImport parentRoute: typeof SurveyImport
@ -296,6 +298,10 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof MonitorMonitorIdEditImport preLoaderRoute: typeof MonitorMonitorIdEditImport
parentRoute: typeof MonitorImport parentRoute: typeof MonitorImport
} }
'/survey/$surveyId/edit': {
preLoaderRoute: typeof SurveySurveyIdEditImport
parentRoute: typeof SurveyImport
}
'/website/$websiteId/config': { '/website/$websiteId/config': {
preLoaderRoute: typeof WebsiteWebsiteIdConfigImport preLoaderRoute: typeof WebsiteWebsiteIdConfigImport
parentRoute: typeof WebsiteImport parentRoute: typeof WebsiteImport
@ -304,6 +310,10 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof MonitorMonitorIdIndexImport preLoaderRoute: typeof MonitorMonitorIdIndexImport
parentRoute: typeof MonitorImport parentRoute: typeof MonitorImport
} }
'/survey/$surveyId/': {
preLoaderRoute: typeof SurveySurveyIdIndexImport
parentRoute: typeof SurveyImport
}
'/website/$websiteId/': { '/website/$websiteId/': {
preLoaderRoute: typeof WebsiteWebsiteIdIndexImport preLoaderRoute: typeof WebsiteWebsiteIdIndexImport
parentRoute: typeof WebsiteImport parentRoute: typeof WebsiteImport
@ -331,7 +341,11 @@ export const routeTree = rootRoute.addChildren([
SettingsProfileRoute, SettingsProfileRoute,
SettingsUsageRoute, SettingsUsageRoute,
]), ]),
SurveyRoute.addChildren([SurveySurveyIdRoute, SurveyAddRoute]), SurveyRoute.addChildren([
SurveyAddRoute,
SurveySurveyIdEditRoute,
SurveySurveyIdIndexRoute,
]),
TelemetryRoute.addChildren([TelemetryTelemetryIdRoute, TelemetryAddRoute]), TelemetryRoute.addChildren([TelemetryTelemetryIdRoute, TelemetryAddRoute]),
WebsiteRoute.addChildren([ WebsiteRoute.addChildren([
WebsiteAddRoute, WebsiteAddRoute,

View File

@ -0,0 +1,71 @@
import { createFileRoute, useNavigate } from '@tanstack/react-router';
import { useTranslation } from '@i18next-toolkit/react';
import { useEvent } from '@/hooks/useEvent';
import { useCurrentWorkspaceId } from '@/store/user';
import { trpc } from '@/api/trpc';
import { Card, CardContent } from '@/components/ui/card';
import { CommonWrapper } from '@/components/CommonWrapper';
import { routeAuthBeforeLoad } from '@/utils/route';
import { Loading } from '@/components/Loading';
import { ErrorTip } from '@/components/ErrorTip';
import { CommonHeader } from '@/components/CommonHeader';
import { ScrollArea } from '@/components/ui/scroll-area';
import {
SurveyEditForm,
SurveyEditFormValues,
} from '@/components/survey/SurveyEditForm';
export const Route = createFileRoute('/survey/$surveyId/edit')({
beforeLoad: routeAuthBeforeLoad,
component: PageComponent,
});
function PageComponent() {
const { t } = useTranslation();
const { surveyId } = Route.useParams<{ surveyId: string }>();
const workspaceId = useCurrentWorkspaceId();
const navigate = useNavigate();
const mutation = trpc.survey.update.useMutation();
const { data: survey, isLoading } = trpc.survey.get.useQuery({
workspaceId,
surveyId,
});
const handleSubmit = useEvent(async (values: SurveyEditFormValues) => {
const res = await mutation.mutateAsync({
...values,
surveyId,
workspaceId,
});
navigate({
to: '/survey/$surveyId',
params: {
surveyId: res.id,
},
replace: true,
});
});
if (isLoading) {
return <Loading />;
}
if (!survey) {
return <ErrorTip />;
}
return (
<CommonWrapper
header={<CommonHeader title={survey.name} desc={t('Edit')} />}
>
<ScrollArea className="h-full overflow-hidden p-4">
<Card>
<CardContent className="pt-4">
<SurveyEditForm defaultValues={survey} onSubmit={handleSubmit} />
</CardContent>
</Card>
</ScrollArea>
</CommonWrapper>
);
}

View File

@ -1,4 +1,9 @@
import { defaultErrorHandler, defaultSuccessHandler, trpc } from '@/api/trpc'; import {
AppRouterOutput,
defaultErrorHandler,
defaultSuccessHandler,
trpc,
} from '@/api/trpc';
import { CommonHeader } from '@/components/CommonHeader'; import { CommonHeader } from '@/components/CommonHeader';
import { CommonWrapper } from '@/components/CommonWrapper'; import { CommonWrapper } from '@/components/CommonWrapper';
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'; import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area';
@ -8,10 +13,18 @@ import { useTranslation } from '@i18next-toolkit/react';
import { createFileRoute, useNavigate } from '@tanstack/react-router'; import { createFileRoute, useNavigate } from '@tanstack/react-router';
import { useEvent } from '@/hooks/useEvent'; import { useEvent } from '@/hooks/useEvent';
import { AlertConfirm } from '@/components/AlertConfirm'; import { AlertConfirm } from '@/components/AlertConfirm';
import { LuTrash } from 'react-icons/lu'; import { LuPencil, LuTrash } from 'react-icons/lu';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { DataTable, createColumnHelper } from '@/components/DataTable';
import { useMemo } from 'react';
export const Route = createFileRoute('/survey/$surveyId')({ type SurveyResultItem =
AppRouterOutput['survey']['resultList']['items'][number];
const columnHelper = createColumnHelper<SurveyResultItem>();
export const Route = createFileRoute('/survey/$surveyId/')({
beforeLoad: routeAuthBeforeLoad, beforeLoad: routeAuthBeforeLoad,
component: PageComponent, component: PageComponent,
}); });
@ -28,6 +41,10 @@ function PageComponent() {
workspaceId, workspaceId,
surveyId, surveyId,
}); });
const { data: resultList } = trpc.survey.resultList.useInfiniteQuery({
workspaceId,
surveyId,
});
const deleteMutation = trpc.survey.delete.useMutation({ const deleteMutation = trpc.survey.delete.useMutation({
onSuccess: defaultSuccessHandler, onSuccess: defaultSuccessHandler,
onError: defaultErrorHandler, onError: defaultErrorHandler,
@ -44,13 +61,47 @@ function PageComponent() {
}); });
}); });
const dataSource = resultList?.pages.map((p) => p.items).flat() ?? [];
const columns = useMemo(() => {
return [
columnHelper.accessor('id', {
header: t('ID'),
size: 150,
}),
...(info?.payload.items.map((item) =>
columnHelper.accessor(`payload.${item.name}`, {
header: item.label,
})
) ?? []),
columnHelper.accessor('createdAt', {
header: t('Created At'),
size: 150,
}),
];
}, [t, info]);
return ( return (
<CommonWrapper <CommonWrapper
header={ header={
<CommonHeader <CommonHeader
title={info?.name ?? ''} title={info?.name ?? ''}
actions={ actions={
<div> <div className="space-x-2">
<Button
variant="outline"
size="icon"
Icon={LuPencil}
onClick={() =>
navigate({
to: '/survey/$surveyId/edit',
params: {
surveyId,
},
})
}
/>
<AlertConfirm <AlertConfirm
title={t('Confirm to delete this survey?')} title={t('Confirm to delete this survey?')}
description={t('Survey name: {{name}} | data count: {{num}}', { description={t('Survey name: {{name}} | data count: {{num}}', {
@ -70,7 +121,18 @@ function PageComponent() {
<ScrollArea className="h-full overflow-hidden p-4"> <ScrollArea className="h-full overflow-hidden p-4">
<ScrollBar orientation="horizontal" /> <ScrollBar orientation="horizontal" />
{/* */} <div className="mb-4 ">
<Card>
<CardHeader>
<CardTitle>{t('Count')}</CardTitle>
</CardHeader>
<CardContent>{count}</CardContent>
</Card>
</div>
<div>
<DataTable columns={columns} data={dataSource} />
</div>
</ScrollArea> </ScrollArea>
</CommonWrapper> </CommonWrapper>
); );

View File

@ -1,7 +1,7 @@
import { z } from 'zod'; import { z } from 'zod';
export function buildCursorResponseSchema( export function buildCursorResponseSchema<T extends z.ZodType>(
itemSchema: z.ZodType, itemSchema: T,
cursorSchema = z.string() cursorSchema = z.string()
) { ) {
return z.object({ return z.object({