feat: survey detail and edit
This commit is contained in:
parent
12cd54eafe
commit
a596011960
@ -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>
|
||||||
|
@ -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,
|
||||||
|
71
src/client/routes/survey/$surveyId/edit.tsx
Normal file
71
src/client/routes/survey/$surveyId/edit.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
@ -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>
|
||||||
);
|
);
|
@ -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({
|
||||||
|
Loading…
Reference in New Issue
Block a user