feat: survey basic fe framework and add new form
This commit is contained in:
parent
27642625ac
commit
010fd00be3
@ -11,7 +11,7 @@ export const TipIcon: React.FC<TipIconProps> = React.memo((props) => {
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<TooltipTrigger type="button">
|
||||
<LuHelpCircle className={className} />
|
||||
</TooltipTrigger>
|
||||
|
||||
|
255
src/client/components/survey/SurveyEditForm.tsx
Normal file
255
src/client/components/survey/SurveyEditForm.tsx
Normal file
@ -0,0 +1,255 @@
|
||||
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
||||
import { useTranslation } from '@i18next-toolkit/react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useEvent, useEventWithLoading } from '@/hooks/useEvent';
|
||||
import { useCurrentWorkspaceId } from '@/store/user';
|
||||
import { defaultErrorHandler, trpc } from '@/api/trpc';
|
||||
import { Card, CardContent, CardFooter } from '@/components/ui/card';
|
||||
import { CommonWrapper } from '@/components/CommonWrapper';
|
||||
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { useForm, useFieldArray } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { generateRandomString } from '@/utils/common';
|
||||
import { LuArrowDown, LuArrowUp, LuMinus, LuPlus } from 'react-icons/lu';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import React, { useState } from 'react';
|
||||
import { TipIcon } from '../TipIcon';
|
||||
import { cn } from '@/utils/style';
|
||||
import { Switch } from '../ui/switch';
|
||||
|
||||
const addFormSchema = z.object({
|
||||
name: z.string(),
|
||||
payload: z.object({
|
||||
items: z.array(
|
||||
z.object({
|
||||
label: z.string(),
|
||||
name: z.string(),
|
||||
type: z.enum(['text', 'select', 'email']),
|
||||
options: z.array(z.string()).optional(),
|
||||
})
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
export type SurveyEditFormValues = z.infer<typeof addFormSchema>;
|
||||
|
||||
function generateDefaultItem() {
|
||||
return {
|
||||
label: 'New Field',
|
||||
name: 'field-' + generateRandomString(4),
|
||||
type: 'text' as const,
|
||||
};
|
||||
}
|
||||
|
||||
interface SurveyEditFormProps {
|
||||
defaultValues?: SurveyEditFormValues;
|
||||
onSubmit: (values: SurveyEditFormValues) => Promise<void>;
|
||||
}
|
||||
export const SurveyEditForm: React.FC<SurveyEditFormProps> = React.memo(
|
||||
(props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [advancedMode, setAdvancedMode] = useState(false);
|
||||
|
||||
const form = useForm<SurveyEditFormValues>({
|
||||
resolver: zodResolver(addFormSchema),
|
||||
defaultValues: props.defaultValues ?? {
|
||||
name: 'New Survey',
|
||||
payload: {
|
||||
items: [generateDefaultItem()],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [handleSubmit, isLoading] = useEventWithLoading(
|
||||
async (values: SurveyEditFormValues) => {
|
||||
await props.onSubmit(values);
|
||||
form.reset();
|
||||
}
|
||||
);
|
||||
|
||||
const { fields, append, swap, insert, remove } = useFieldArray({
|
||||
control: form.control,
|
||||
name: 'payload.items',
|
||||
});
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-8">
|
||||
<Card>
|
||||
<CardContent className="pt-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Survey Name')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Survey Name to Display')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="border-muted mt-2 rounded-lg border p-4">
|
||||
<h2 className="mb-2 font-bold leading-6">{t('Form Info')}</h2>
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Switch
|
||||
checked={advancedMode}
|
||||
onCheckedChange={(checked) => setAdvancedMode(checked)}
|
||||
/>
|
||||
<div className="text-sm">{t('Advanced Mode')}</div>
|
||||
</div>
|
||||
|
||||
{fields.map((field, i) => (
|
||||
<div key={field.id} className="mb-2 flex gap-1">
|
||||
<FormItem>
|
||||
<FormLabel>{t('Display Label')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...form.register(`payload.items.${i}.label`)} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
{advancedMode && (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t('Name')}
|
||||
<TipIcon
|
||||
className="ml-1"
|
||||
content={t('Use for storage')}
|
||||
/>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...form.register(`payload.items.${i}.name`)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
|
||||
<FormItem>
|
||||
<FormLabel>{t('Type')}</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
defaultValue="text"
|
||||
onValueChange={(val) =>
|
||||
form.setValue(`payload.items.${i}.type`, val as any)
|
||||
}
|
||||
>
|
||||
<SelectTrigger
|
||||
className="w-[100px]"
|
||||
{...form.register(`payload.items.${i}.type`)}
|
||||
>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="text">{t('Text')}</SelectItem>
|
||||
<SelectItem value="email">{t('Email')}</SelectItem>
|
||||
{/* <SelectItem value="select">
|
||||
{t('Select')}
|
||||
</SelectItem> */}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
{/* actions */}
|
||||
<div className="grid min-w-10 grid-flow-col grid-cols-2 grid-rows-2 gap-0.5 self-end">
|
||||
<Button
|
||||
type="button"
|
||||
variant="dashed"
|
||||
size="icon"
|
||||
className="h-5 w-5 text-xs"
|
||||
Icon={LuMinus}
|
||||
onClick={() => {
|
||||
remove(i);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="dashed"
|
||||
size="icon"
|
||||
className="h-5 w-5 text-xs"
|
||||
Icon={LuPlus}
|
||||
onClick={() => {
|
||||
insert(i + 1, generateDefaultItem());
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="dashed"
|
||||
size="icon"
|
||||
className={cn(
|
||||
'h-5 w-5 text-xs',
|
||||
i === 0 && 'opacity-0'
|
||||
)}
|
||||
Icon={LuArrowUp}
|
||||
onClick={() => {
|
||||
swap(i, i - 1);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="dashed"
|
||||
size="icon"
|
||||
className={cn(
|
||||
'h-5 w-5 text-xs',
|
||||
i === fields.length - 1 && 'opacity-0'
|
||||
)}
|
||||
Icon={LuArrowDown}
|
||||
onClick={() => {
|
||||
swap(i, i + 1);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="mt-2 flex justify-end">
|
||||
<Button
|
||||
variant="dashed"
|
||||
type="button"
|
||||
Icon={LuPlus}
|
||||
size="icon"
|
||||
onClick={() => append(generateDefaultItem())}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
<CardFooter>
|
||||
<Button type="submit" loading={isLoading}>
|
||||
{t('Create')}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
);
|
||||
SurveyEditForm.displayName = 'SurveyEditForm';
|
@ -4,6 +4,7 @@ import {
|
||||
LuFilePieChart,
|
||||
LuMonitorDot,
|
||||
LuServer,
|
||||
LuTableProperties,
|
||||
LuWifi,
|
||||
} from 'react-icons/lu';
|
||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||
@ -97,6 +98,12 @@ export const DesktopLayout: React.FC<LayoutProps> = React.memo((props) => {
|
||||
icon: LuFilePieChart,
|
||||
to: '/page',
|
||||
},
|
||||
{
|
||||
title: t('Survey'),
|
||||
label: String(serviceCount?.survey ?? ''),
|
||||
icon: LuTableProperties,
|
||||
to: '/survey',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Separator />
|
||||
|
@ -13,6 +13,7 @@
|
||||
import { Route as rootRoute } from './routes/__root'
|
||||
import { Route as WebsiteImport } from './routes/website'
|
||||
import { Route as TelemetryImport } from './routes/telemetry'
|
||||
import { Route as SurveyImport } from './routes/survey'
|
||||
import { Route as SettingsImport } from './routes/settings'
|
||||
import { Route as ServerImport } from './routes/server'
|
||||
import { Route as RegisterImport } from './routes/register'
|
||||
@ -25,6 +26,8 @@ import { Route as WebsiteOverviewImport } from './routes/website/overview'
|
||||
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'
|
||||
@ -50,6 +53,11 @@ const TelemetryRoute = TelemetryImport.update({
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const SurveyRoute = SurveyImport.update({
|
||||
path: '/survey',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const SettingsRoute = SettingsImport.update({
|
||||
path: '/settings',
|
||||
getParentRoute: () => rootRoute,
|
||||
@ -110,6 +118,16 @@ const TelemetryTelemetryIdRoute = TelemetryTelemetryIdImport.update({
|
||||
getParentRoute: () => TelemetryRoute,
|
||||
} as any)
|
||||
|
||||
const SurveyAddRoute = SurveyAddImport.update({
|
||||
path: '/add',
|
||||
getParentRoute: () => SurveyRoute,
|
||||
} as any)
|
||||
|
||||
const SurveySurveyIdRoute = SurveySurveyIdImport.update({
|
||||
path: '/$surveyId',
|
||||
getParentRoute: () => SurveyRoute,
|
||||
} as any)
|
||||
|
||||
const StatusSlugRoute = StatusSlugImport.update({
|
||||
path: '/status/$slug',
|
||||
getParentRoute: () => rootRoute,
|
||||
@ -206,6 +224,10 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof SettingsImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/survey': {
|
||||
preLoaderRoute: typeof SurveyImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/telemetry': {
|
||||
preLoaderRoute: typeof TelemetryImport
|
||||
parentRoute: typeof rootRoute
|
||||
@ -246,6 +268,14 @@ 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
|
||||
}
|
||||
'/telemetry/$telemetryId': {
|
||||
preLoaderRoute: typeof TelemetryTelemetryIdImport
|
||||
parentRoute: typeof TelemetryImport
|
||||
@ -301,6 +331,7 @@ export const routeTree = rootRoute.addChildren([
|
||||
SettingsProfileRoute,
|
||||
SettingsUsageRoute,
|
||||
]),
|
||||
SurveyRoute.addChildren([SurveySurveyIdRoute, SurveyAddRoute]),
|
||||
TelemetryRoute.addChildren([TelemetryTelemetryIdRoute, TelemetryAddRoute]),
|
||||
WebsiteRoute.addChildren([
|
||||
WebsiteAddRoute,
|
||||
|
89
src/client/routes/survey.tsx
Normal file
89
src/client/routes/survey.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
import { trpc } from '@/api/trpc';
|
||||
import { CommonHeader } from '@/components/CommonHeader';
|
||||
import { CommonList } from '@/components/CommonList';
|
||||
import { CommonWrapper } from '@/components/CommonWrapper';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useDataReady } from '@/hooks/useDataReady';
|
||||
import { useEvent } from '@/hooks/useEvent';
|
||||
import { LayoutV2 } from '@/pages/LayoutV2';
|
||||
import { useCurrentWorkspaceId } from '@/store/user';
|
||||
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||
import { Trans, useTranslation } from '@i18next-toolkit/react';
|
||||
import {
|
||||
createFileRoute,
|
||||
useNavigate,
|
||||
useRouterState,
|
||||
} from '@tanstack/react-router';
|
||||
import { LuPlus } from 'react-icons/lu';
|
||||
|
||||
export const Route = createFileRoute('/survey')({
|
||||
beforeLoad: routeAuthBeforeLoad,
|
||||
component: PageComponent,
|
||||
});
|
||||
|
||||
function PageComponent() {
|
||||
const workspaceId = useCurrentWorkspaceId();
|
||||
const { t } = useTranslation();
|
||||
const { data = [] } = trpc.survey.all.useQuery({
|
||||
workspaceId,
|
||||
});
|
||||
const { data: allResultCount = {} } = trpc.survey.allResultCount.useQuery({
|
||||
workspaceId,
|
||||
});
|
||||
const navigate = useNavigate();
|
||||
const pathname = useRouterState({
|
||||
select: (state) => state.location.pathname,
|
||||
});
|
||||
|
||||
const items = data.map((item) => ({
|
||||
id: item.id,
|
||||
title: item.name,
|
||||
number: allResultCount[item.id] ?? 0,
|
||||
href: `/survey/${item.id}`,
|
||||
}));
|
||||
|
||||
useDataReady(
|
||||
() => data.length > 0,
|
||||
() => {
|
||||
if (pathname === Route.fullPath) {
|
||||
navigate({
|
||||
to: '/survey/$surveyId',
|
||||
params: {
|
||||
surveyId: data[0].id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const handleClickAdd = useEvent(() => {
|
||||
navigate({
|
||||
to: '/survey/add',
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<LayoutV2
|
||||
list={
|
||||
<CommonWrapper
|
||||
header={
|
||||
<CommonHeader
|
||||
title={t('Survey')}
|
||||
actions={
|
||||
<Button
|
||||
variant="outline"
|
||||
Icon={LuPlus}
|
||||
onClick={handleClickAdd}
|
||||
>
|
||||
{t('Add')}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<CommonList hasSearch={true} items={items} />
|
||||
</CommonWrapper>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
54
src/client/routes/survey/$surveyId.tsx
Normal file
54
src/client/routes/survey/$surveyId.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import { defaultErrorHandler, defaultSuccessHandler, trpc } from '@/api/trpc';
|
||||
import { CommonHeader } from '@/components/CommonHeader';
|
||||
import { CommonWrapper } from '@/components/CommonWrapper';
|
||||
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area';
|
||||
import { useGlobalRangeDate } from '@/hooks/useGlobalRangeDate';
|
||||
import { useCurrentWorkspaceId } from '@/store/user';
|
||||
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||
import { useTranslation } from '@i18next-toolkit/react';
|
||||
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
||||
import { useEvent } from '@/hooks/useEvent';
|
||||
|
||||
export const Route = createFileRoute('/survey/$surveyId')({
|
||||
beforeLoad: routeAuthBeforeLoad,
|
||||
component: PageComponent,
|
||||
});
|
||||
|
||||
function PageComponent() {
|
||||
const { surveyId } = Route.useParams<{ surveyId: string }>();
|
||||
const workspaceId = useCurrentWorkspaceId();
|
||||
const { t } = useTranslation();
|
||||
const { data: info } = trpc.survey.get.useQuery({
|
||||
workspaceId,
|
||||
surveyId,
|
||||
});
|
||||
const { data: count } = trpc.survey.count.useQuery({
|
||||
workspaceId,
|
||||
surveyId,
|
||||
});
|
||||
const deleteMutation = trpc.survey.delete.useMutation({
|
||||
onSuccess: defaultSuccessHandler,
|
||||
onError: defaultErrorHandler,
|
||||
});
|
||||
const trpcUtils = trpc.useUtils();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleDelete = useEvent(async () => {
|
||||
await deleteMutation.mutateAsync({ workspaceId, surveyId });
|
||||
trpcUtils.survey.all.refetch();
|
||||
navigate({
|
||||
to: '/survey',
|
||||
replace: true,
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<CommonWrapper header={<CommonHeader title={info?.name ?? ''} />}>
|
||||
<ScrollArea className="h-full overflow-hidden p-4">
|
||||
<ScrollBar orientation="horizontal" />
|
||||
|
||||
{/* */}
|
||||
</ScrollArea>
|
||||
</CommonWrapper>
|
||||
);
|
||||
}
|
53
src/client/routes/survey/add.tsx
Normal file
53
src/client/routes/survey/add.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
||||
import { useTranslation } from '@i18next-toolkit/react';
|
||||
import { useEvent } from '@/hooks/useEvent';
|
||||
import { useCurrentWorkspaceId } from '@/store/user';
|
||||
import { defaultErrorHandler, trpc } from '@/api/trpc';
|
||||
import { CommonWrapper } from '@/components/CommonWrapper';
|
||||
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||
import {
|
||||
SurveyEditForm,
|
||||
SurveyEditFormValues,
|
||||
} from '@/components/survey/SurveyEditForm';
|
||||
|
||||
export const Route = createFileRoute('/survey/add')({
|
||||
beforeLoad: routeAuthBeforeLoad,
|
||||
component: PageComponent,
|
||||
});
|
||||
|
||||
function PageComponent() {
|
||||
const { t } = useTranslation();
|
||||
const workspaceId = useCurrentWorkspaceId();
|
||||
const createMutation = trpc.survey.create.useMutation({
|
||||
onError: defaultErrorHandler,
|
||||
});
|
||||
const utils = trpc.useUtils();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const onSubmit = useEvent(async (values: SurveyEditFormValues) => {
|
||||
const res = await createMutation.mutateAsync({
|
||||
workspaceId,
|
||||
name: values.name,
|
||||
payload: values.payload,
|
||||
});
|
||||
|
||||
utils.survey.all.refetch();
|
||||
|
||||
navigate({
|
||||
to: '/survey/$surveyId',
|
||||
params: {
|
||||
surveyId: res.id,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<CommonWrapper
|
||||
header={<h1 className="text-xl font-bold">{t('Add Survey')}</h1>}
|
||||
>
|
||||
<div className="p-4">
|
||||
<SurveyEditForm onSubmit={onSubmit} />
|
||||
</div>
|
||||
</CommonWrapper>
|
||||
);
|
||||
}
|
@ -65,7 +65,7 @@ function TelemetryAddComponent() {
|
||||
|
||||
return (
|
||||
<CommonWrapper
|
||||
header={<h1 className="text-xl font-bold">{t('Add Website')}</h1>}
|
||||
header={<h1 className="text-xl font-bold">{t('Add Telemetry')}</h1>}
|
||||
>
|
||||
<div className="p-4">
|
||||
<Form {...form}>
|
||||
|
@ -38,3 +38,14 @@ export function formatShortTime(val: number, formats = ['m', 's'], space = '') {
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
export function generateRandomString(length: number): string {
|
||||
const characters =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
const charactersLength = characters.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import { serverStatusRouter } from './serverStatus';
|
||||
import { auditLogRouter } from './auditLog';
|
||||
import { billingRouter } from './billing';
|
||||
import { telemetryRouter } from './telemetry';
|
||||
import { surveyRouter } from './survey';
|
||||
|
||||
export const appRouter = router({
|
||||
global: globalRouter,
|
||||
@ -18,6 +19,7 @@ export const appRouter = router({
|
||||
notification: notificationRouter,
|
||||
monitor: monitorRouter,
|
||||
telemetry: telemetryRouter,
|
||||
survey: surveyRouter,
|
||||
serverStatus: serverStatusRouter,
|
||||
auditLog: auditLogRouter,
|
||||
billing: billingRouter,
|
||||
|
@ -16,7 +16,7 @@ import { SurveyPayloadSchema } from '../../prisma/zod/schemas';
|
||||
import { buildCursorResponseSchema } from '../../utils/schema';
|
||||
import { fetchDataByCursor } from '../../utils/prisma';
|
||||
|
||||
export const telemetryRouter = router({
|
||||
export const surveyRouter = router({
|
||||
all: workspaceProcedure
|
||||
.meta(
|
||||
buildSurveyOpenapi({
|
||||
@ -89,6 +89,28 @@ export const telemetryRouter = router({
|
||||
|
||||
return count;
|
||||
}),
|
||||
allResultCount: workspaceProcedure
|
||||
.meta(
|
||||
buildSurveyOpenapi({
|
||||
method: 'GET',
|
||||
path: '/allResultCount',
|
||||
})
|
||||
)
|
||||
.output(z.record(z.string(), z.number()))
|
||||
.query(async () => {
|
||||
const res = await prisma.surveyResult.groupBy({
|
||||
by: ['surveyId'],
|
||||
_count: true,
|
||||
});
|
||||
|
||||
return res.reduce<Record<string, number>>((prev, item) => {
|
||||
if (item.surveyId) {
|
||||
prev[item.surveyId] = item._count;
|
||||
}
|
||||
|
||||
return prev;
|
||||
}, {});
|
||||
}),
|
||||
submit: publicProcedure
|
||||
.meta(
|
||||
buildSurveyOpenapi({
|
||||
|
@ -50,12 +50,13 @@ export const workspaceRouter = router({
|
||||
server: z.number(),
|
||||
telemetry: z.number(),
|
||||
page: z.number(),
|
||||
survey: z.number(),
|
||||
})
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
const { workspaceId } = input;
|
||||
|
||||
const [website, monitor, telemetry, page] = await Promise.all([
|
||||
const [website, monitor, telemetry, page, survey] = await Promise.all([
|
||||
prisma.website.count({
|
||||
where: {
|
||||
workspaceId,
|
||||
@ -76,6 +77,11 @@ export const workspaceRouter = router({
|
||||
workspaceId,
|
||||
},
|
||||
}),
|
||||
prisma.survey.count({
|
||||
where: {
|
||||
workspaceId,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const server = getServerCount(workspaceId);
|
||||
@ -86,6 +92,7 @@ export const workspaceRouter = router({
|
||||
server,
|
||||
telemetry,
|
||||
page,
|
||||
survey,
|
||||
};
|
||||
}),
|
||||
updateDashboardOrder: workspaceOwnerProcedure
|
||||
|
Loading…
Reference in New Issue
Block a user