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>
<Button type="submit" loading={isLoading}>
{t('Create')}
{props.defaultValues ? t('Update') : t('Create')}
</Button>
</CardFooter>
</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 TelemetryTelemetryIdImport } from './routes/telemetry/$telemetryId'
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 SettingsUsageImport } from './routes/settings/usage'
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 MonitorAddImport } from './routes/monitor/add'
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 WebsiteWebsiteIdConfigImport } from './routes/website/$websiteId/config'
import { Route as SurveySurveyIdEditImport } from './routes/survey/$surveyId/edit'
import { Route as MonitorMonitorIdEditImport } from './routes/monitor/$monitorId/edit'
// Create/Update Routes
@ -123,11 +124,6 @@ const SurveyAddRoute = SurveyAddImport.update({
getParentRoute: () => SurveyRoute,
} as any)
const SurveySurveyIdRoute = SurveySurveyIdImport.update({
path: '/$surveyId',
getParentRoute: () => SurveyRoute,
} as any)
const StatusSlugRoute = StatusSlugImport.update({
path: '/status/$slug',
getParentRoute: () => rootRoute,
@ -173,6 +169,11 @@ const WebsiteWebsiteIdIndexRoute = WebsiteWebsiteIdIndexImport.update({
getParentRoute: () => WebsiteRoute,
} as any)
const SurveySurveyIdIndexRoute = SurveySurveyIdIndexImport.update({
path: '/$surveyId/',
getParentRoute: () => SurveyRoute,
} as any)
const MonitorMonitorIdIndexRoute = MonitorMonitorIdIndexImport.update({
path: '/$monitorId/',
getParentRoute: () => MonitorRoute,
@ -183,6 +184,11 @@ const WebsiteWebsiteIdConfigRoute = WebsiteWebsiteIdConfigImport.update({
getParentRoute: () => WebsiteRoute,
} as any)
const SurveySurveyIdEditRoute = SurveySurveyIdEditImport.update({
path: '/$surveyId/edit',
getParentRoute: () => SurveyRoute,
} as any)
const MonitorMonitorIdEditRoute = MonitorMonitorIdEditImport.update({
path: '/$monitorId/edit',
getParentRoute: () => MonitorRoute,
@ -268,10 +274,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof StatusSlugImport
parentRoute: typeof rootRoute
}
'/survey/$surveyId': {
preLoaderRoute: typeof SurveySurveyIdImport
parentRoute: typeof SurveyImport
}
'/survey/add': {
preLoaderRoute: typeof SurveyAddImport
parentRoute: typeof SurveyImport
@ -296,6 +298,10 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof MonitorMonitorIdEditImport
parentRoute: typeof MonitorImport
}
'/survey/$surveyId/edit': {
preLoaderRoute: typeof SurveySurveyIdEditImport
parentRoute: typeof SurveyImport
}
'/website/$websiteId/config': {
preLoaderRoute: typeof WebsiteWebsiteIdConfigImport
parentRoute: typeof WebsiteImport
@ -304,6 +310,10 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof MonitorMonitorIdIndexImport
parentRoute: typeof MonitorImport
}
'/survey/$surveyId/': {
preLoaderRoute: typeof SurveySurveyIdIndexImport
parentRoute: typeof SurveyImport
}
'/website/$websiteId/': {
preLoaderRoute: typeof WebsiteWebsiteIdIndexImport
parentRoute: typeof WebsiteImport
@ -331,7 +341,11 @@ export const routeTree = rootRoute.addChildren([
SettingsProfileRoute,
SettingsUsageRoute,
]),
SurveyRoute.addChildren([SurveySurveyIdRoute, SurveyAddRoute]),
SurveyRoute.addChildren([
SurveyAddRoute,
SurveySurveyIdEditRoute,
SurveySurveyIdIndexRoute,
]),
TelemetryRoute.addChildren([TelemetryTelemetryIdRoute, TelemetryAddRoute]),
WebsiteRoute.addChildren([
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 { CommonWrapper } from '@/components/CommonWrapper';
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 { useEvent } from '@/hooks/useEvent';
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 { 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,
component: PageComponent,
});
@ -28,6 +41,10 @@ function PageComponent() {
workspaceId,
surveyId,
});
const { data: resultList } = trpc.survey.resultList.useInfiniteQuery({
workspaceId,
surveyId,
});
const deleteMutation = trpc.survey.delete.useMutation({
onSuccess: defaultSuccessHandler,
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 (
<CommonWrapper
header={
<CommonHeader
title={info?.name ?? ''}
actions={
<div>
<div className="space-x-2">
<Button
variant="outline"
size="icon"
Icon={LuPencil}
onClick={() =>
navigate({
to: '/survey/$surveyId/edit',
params: {
surveyId,
},
})
}
/>
<AlertConfirm
title={t('Confirm to delete this survey?')}
description={t('Survey name: {{name}} | data count: {{num}}', {
@ -70,7 +121,18 @@ function PageComponent() {
<ScrollArea className="h-full overflow-hidden p-4">
<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>
</CommonWrapper>
);

View File

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