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>
|
||||
<Button type="submit" loading={isLoading}>
|
||||
{t('Create')}
|
||||
{props.defaultValues ? t('Update') : t('Create')}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
@ -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,
|
||||
|
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 { 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>
|
||||
);
|
@ -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({
|
||||
|
Loading…
Reference in New Issue
Block a user