mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-06 00:49:42 -06:00
feat: custom data fetching for surveys table (#3318)
This commit is contained in:
committed by
GitHub
parent
a6a815c014
commit
abc4c7f156
@@ -1,5 +1,6 @@
|
||||
"use server";
|
||||
|
||||
import { getSurvey, getSurveys } from "@/app/(app)/environments/[environmentId]/surveys/lib/surveys";
|
||||
import { z } from "zod";
|
||||
import { authenticatedActionClient } from "@formbricks/lib/actionClient";
|
||||
import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
|
||||
@@ -8,12 +9,7 @@ import {
|
||||
getOrganizationIdFromSurveyId,
|
||||
} from "@formbricks/lib/organization/utils";
|
||||
import { getProducts } from "@formbricks/lib/product/service";
|
||||
import {
|
||||
copySurveyToOtherEnvironment,
|
||||
deleteSurvey,
|
||||
getSurvey,
|
||||
getSurveys,
|
||||
} from "@formbricks/lib/survey/service";
|
||||
import { copySurveyToOtherEnvironment, deleteSurvey } from "@formbricks/lib/survey/service";
|
||||
import { generateSurveySingleUseId } from "@formbricks/lib/utils/singleUseSurveys";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { ZSurveyFilterCriteria } from "@formbricks/types/surveys/types";
|
||||
@@ -1,14 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { copySurveyToOtherEnvironmentAction } from "@/app/(app)/environments/[environmentId]/surveys/actions";
|
||||
import { TSurvey } from "@/app/(app)/environments/[environmentId]/surveys/types/surveys";
|
||||
import {
|
||||
TSurveyCopyFormData,
|
||||
ZSurveyCopyFormValidation,
|
||||
} from "@/app/(app)/environments/[environmentId]/surveys/types/surveys";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useFieldArray, useForm } from "react-hook-form";
|
||||
import toast from "react-hot-toast";
|
||||
import { TProduct } from "@formbricks/types/product";
|
||||
import { TSurvey, TSurveyCopyFormData, ZSurveyCopyFormValidation } from "@formbricks/types/surveys/types";
|
||||
import { Button } from "../../Button";
|
||||
import { Checkbox } from "../../Checkbox";
|
||||
import { FormControl, FormField, FormItem, FormProvider } from "../../Form";
|
||||
import { Label } from "../../Label";
|
||||
import { TooltipRenderer } from "../../Tooltip";
|
||||
import { copySurveyToOtherEnvironmentAction } from "../actions";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Checkbox } from "@formbricks/ui/components/Checkbox";
|
||||
import { FormControl, FormField, FormItem, FormProvider } from "@formbricks/ui/components/Form";
|
||||
import { Label } from "@formbricks/ui/components/Label";
|
||||
import { TooltipRenderer } from "@formbricks/ui/components/Tooltip";
|
||||
|
||||
export const CopySurveyForm = ({
|
||||
defaultProducts,
|
||||
@@ -1,6 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { TSurvey } from "@/app/(app)/environments/[environmentId]/surveys/types/surveys";
|
||||
import { MousePointerClickIcon } from "lucide-react";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { Modal } from "../../Modal";
|
||||
import { Modal } from "@formbricks/ui/components/Modal";
|
||||
import SurveyCopyOptions from "./SurveyCopyOptions";
|
||||
|
||||
interface CopySurveyModalProps {
|
||||
@@ -1,5 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { TSortOption, TSurveyFilters } from "@formbricks/types/surveys/types";
|
||||
import { DropdownMenuItem } from "../../DropdownMenu";
|
||||
import { DropdownMenuItem } from "@formbricks/ui/components/DropdownMenu";
|
||||
|
||||
interface SortOptionProps {
|
||||
option: TSortOption;
|
||||
@@ -0,0 +1,124 @@
|
||||
"use client";
|
||||
|
||||
import { generateSingleUseIdAction } from "@/app/(app)/environments/[environmentId]/surveys/actions";
|
||||
import { SurveyTypeIndicator } from "@/app/(app)/environments/[environmentId]/surveys/components/SurveyTypeIndicator";
|
||||
import { TSurvey } from "@/app/(app)/environments/[environmentId]/surveys/types/surveys";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { getFormattedErrorMessage } from "@formbricks/lib/actionClient/helper";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { convertDateString, timeSince } from "@formbricks/lib/time";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { SurveyStatusIndicator } from "@formbricks/ui/components/SurveyStatusIndicator";
|
||||
import { SurveyDropDownMenu } from "./SurveyDropdownMenu";
|
||||
|
||||
interface SurveyCardProps {
|
||||
survey: TSurvey;
|
||||
environment: TEnvironment;
|
||||
otherEnvironment: TEnvironment;
|
||||
isViewer: boolean;
|
||||
WEBAPP_URL: string;
|
||||
duplicateSurvey: (survey: TSurvey) => void;
|
||||
deleteSurvey: (surveyId: string) => void;
|
||||
}
|
||||
export const SurveyCard = ({
|
||||
survey,
|
||||
environment,
|
||||
otherEnvironment,
|
||||
isViewer,
|
||||
WEBAPP_URL,
|
||||
deleteSurvey,
|
||||
duplicateSurvey,
|
||||
}: SurveyCardProps) => {
|
||||
const isSurveyCreationDeletionDisabled = isViewer;
|
||||
|
||||
const surveyStatusLabel = useMemo(() => {
|
||||
if (survey.status === "inProgress") return "In Progress";
|
||||
else if (survey.status === "scheduled") return "Scheduled";
|
||||
else if (survey.status === "completed") return "Completed";
|
||||
else if (survey.status === "draft") return "Draft";
|
||||
else if (survey.status === "paused") return "Paused";
|
||||
}, [survey]);
|
||||
|
||||
const [singleUseId, setSingleUseId] = useState<string | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSingleUseId = async () => {
|
||||
if (survey.singleUse?.enabled) {
|
||||
const generateSingleUseIdResponse = await generateSingleUseIdAction({
|
||||
surveyId: survey.id,
|
||||
isEncrypted: !!survey.singleUse?.isEncrypted,
|
||||
});
|
||||
if (generateSingleUseIdResponse?.data) {
|
||||
setSingleUseId(generateSingleUseIdResponse.data);
|
||||
} else {
|
||||
const errorMessage = getFormattedErrorMessage(generateSingleUseIdResponse);
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
} else {
|
||||
setSingleUseId(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
fetchSingleUseId();
|
||||
}, [survey]);
|
||||
|
||||
const linkHref = useMemo(() => {
|
||||
return survey.status === "draft"
|
||||
? `/environments/${environment.id}/surveys/${survey.id}/edit`
|
||||
: `/environments/${environment.id}/surveys/${survey.id}/summary`;
|
||||
}, [survey.status, survey.id, environment.id]);
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={linkHref}
|
||||
key={survey.id}
|
||||
className="relative grid w-full grid-cols-8 place-items-center gap-3 rounded-xl border border-slate-200 bg-white p-4 shadow-sm transition-all ease-in-out hover:scale-[101%]">
|
||||
<div className="col-span-1 flex max-w-full items-center justify-self-start text-sm font-medium text-slate-900">
|
||||
<div className="w-full truncate">{survey.name}</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"col-span-1 flex w-fit items-center gap-2 rounded-full py-1 pl-1 pr-2 text-sm text-slate-800",
|
||||
surveyStatusLabel === "Scheduled" && "bg-slate-200",
|
||||
surveyStatusLabel === "In Progress" && "bg-emerald-50",
|
||||
surveyStatusLabel === "Completed" && "bg-slate-200",
|
||||
surveyStatusLabel === "Draft" && "bg-slate-100",
|
||||
surveyStatusLabel === "Paused" && "bg-slate-100"
|
||||
)}>
|
||||
<SurveyStatusIndicator status={survey.status} /> {surveyStatusLabel}{" "}
|
||||
</div>
|
||||
<div className="col-span-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-600">
|
||||
{survey.responseCount}
|
||||
</div>
|
||||
<div className="col-span-1 flex justify-between">
|
||||
<SurveyTypeIndicator type={survey.type} />
|
||||
</div>
|
||||
|
||||
<div className="col-span-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-600">
|
||||
{convertDateString(survey.createdAt.toString())}
|
||||
</div>
|
||||
<div className="col-span-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-600">
|
||||
{timeSince(survey.updatedAt.toString())}
|
||||
</div>
|
||||
<div className="col-span-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-600">
|
||||
{survey.creator ? survey.creator.name : "-"}
|
||||
</div>
|
||||
<div className="col-span-1 place-self-end">
|
||||
<SurveyDropDownMenu
|
||||
survey={survey}
|
||||
key={`surveys-${survey.id}`}
|
||||
environmentId={environment.id}
|
||||
environment={environment}
|
||||
otherEnvironment={otherEnvironment!}
|
||||
webAppUrl={WEBAPP_URL}
|
||||
singleUseId={singleUseId}
|
||||
isSurveyCreationDeletionDisabled={isSurveyCreationDeletionDisabled}
|
||||
duplicateSurvey={duplicateSurvey}
|
||||
deleteSurvey={deleteSurvey}
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@@ -1,10 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { getProductsByEnvironmentIdAction } from "@/app/(app)/environments/[environmentId]/surveys/actions";
|
||||
import { TSurvey } from "@/app/(app)/environments/[environmentId]/surveys/types/surveys";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { getFormattedErrorMessage } from "@formbricks/lib/actionClient/helper";
|
||||
import { TProduct } from "@formbricks/types/product";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { getProductsByEnvironmentIdAction } from "../actions";
|
||||
import { CopySurveyForm } from "./CopySurveyForm";
|
||||
|
||||
interface SurveyCopyOptionsProps {
|
||||
@@ -1,5 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
copySurveyToOtherEnvironmentAction,
|
||||
deleteSurveyAction,
|
||||
} from "@/app/(app)/environments/[environmentId]/surveys/actions";
|
||||
import { getSurveyAction } from "@/app/(app)/environments/[environmentId]/surveys/actions";
|
||||
import { TSurvey } from "@/app/(app)/environments/[environmentId]/surveys/types/surveys";
|
||||
import { ArrowUpFromLineIcon, CopyIcon, EyeIcon, LinkIcon, SquarePenIcon, TrashIcon } from "lucide-react";
|
||||
import { MoreVertical } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
@@ -8,16 +14,14 @@ import { useMemo, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { getFormattedErrorMessage } from "@formbricks/lib/actionClient/helper";
|
||||
import type { TEnvironment } from "@formbricks/types/environment";
|
||||
import type { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { DeleteDialog } from "../../DeleteDialog";
|
||||
import { DeleteDialog } from "@formbricks/ui/components/DeleteDialog";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "../../DropdownMenu";
|
||||
import { copySurveyToOtherEnvironmentAction, deleteSurveyAction, getSurveyAction } from "../actions";
|
||||
} from "@formbricks/ui/components/DropdownMenu";
|
||||
import { CopySurveyModal } from "./CopySurveyModal";
|
||||
|
||||
interface SurveyDropDownMenuProps {
|
||||
@@ -49,11 +53,11 @@ export const SurveyDropDownMenu = ({
|
||||
|
||||
const surveyUrl = useMemo(() => webAppUrl + "/s/" + survey.id, [survey.id, webAppUrl]);
|
||||
|
||||
const handleDeleteSurvey = async (survey: TSurvey) => {
|
||||
const handleDeleteSurvey = async (surveyId: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await deleteSurveyAction({ surveyId: survey.id });
|
||||
deleteSurvey(survey.id);
|
||||
await deleteSurveyAction({ surveyId });
|
||||
deleteSurvey(surveyId);
|
||||
router.refresh();
|
||||
setDeleteDialogOpen(false);
|
||||
toast.success("Survey deleted successfully.");
|
||||
@@ -206,7 +210,7 @@ export const SurveyDropDownMenu = ({
|
||||
deleteWhat="Survey"
|
||||
open={isDeleteDialogOpen}
|
||||
setOpen={setDeleteDialogOpen}
|
||||
onDelete={() => handleDeleteSurvey(survey)}
|
||||
onDelete={() => handleDeleteSurvey(survey.id)}
|
||||
text="Are you sure you want to delete this survey and all of its responses? This action cannot be undone."
|
||||
/>
|
||||
)}
|
||||
@@ -1,7 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { ChevronDownIcon } from "lucide-react";
|
||||
import { TFilterOption } from "@formbricks/types/surveys/types";
|
||||
import { Checkbox } from "../../Checkbox";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../../DropdownMenu";
|
||||
import { Checkbox } from "@formbricks/ui/components/Checkbox";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@formbricks/ui/components/DropdownMenu";
|
||||
|
||||
interface SurveyFilterDropdownProps {
|
||||
title: string;
|
||||
@@ -1,20 +1,22 @@
|
||||
import { ChevronDownIcon, Equal, Grid2X2, X } from "lucide-react";
|
||||
"use client";
|
||||
|
||||
import { SortOption } from "@/app/(app)/environments/[environmentId]/surveys/components/SortOption";
|
||||
import { initialFilters } from "@/app/(app)/environments/[environmentId]/surveys/components/SurveyList";
|
||||
import { ChevronDownIcon, X } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useDebounce } from "react-use";
|
||||
import { FORMBRICKS_SURVEYS_ORIENTATION_KEY_LS } from "@formbricks/lib/localStorage";
|
||||
import { TProductConfigChannel } from "@formbricks/types/product";
|
||||
import { TFilterOption, TSortOption, TSurveyFilters } from "@formbricks/types/surveys/types";
|
||||
import { initialFilters } from "..";
|
||||
import { Button } from "../../Button";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "../../DropdownMenu";
|
||||
import { SearchBar } from "../../SearchBar";
|
||||
import { TooltipRenderer } from "../../Tooltip";
|
||||
import { SortOption } from "./SortOption";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from "@formbricks/ui/components/DropdownMenu";
|
||||
import { SearchBar } from "@formbricks/ui/components/SearchBar";
|
||||
import { SurveyFilterDropdown } from "./SurveyFilterDropdown";
|
||||
|
||||
interface SurveyFilterProps {
|
||||
orientation: string;
|
||||
setOrientation: (orientation: string) => void;
|
||||
surveyFilters: TSurveyFilters;
|
||||
setSurveyFilters: React.Dispatch<React.SetStateAction<TSurveyFilters>>;
|
||||
currentProductChannel: TProductConfigChannel;
|
||||
@@ -52,13 +54,7 @@ const sortOptions: TSortOption[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const getToolTipContent = (orientation: string) => {
|
||||
return <div>{orientation} View</div>;
|
||||
};
|
||||
|
||||
export const SurveyFilters = ({
|
||||
orientation,
|
||||
setOrientation,
|
||||
surveyFilters,
|
||||
setSurveyFilters,
|
||||
currentProductChannel,
|
||||
@@ -73,16 +69,7 @@ export const SurveyFilters = ({
|
||||
const typeOptions: TFilterOption[] = [
|
||||
{ label: "Link", value: "link" },
|
||||
{ label: "App", value: "app" },
|
||||
{ label: "Website", value: "website" },
|
||||
].filter((option) => {
|
||||
if (currentProductChannel === "website") {
|
||||
return option.value !== "app";
|
||||
} else if (currentProductChannel === "app") {
|
||||
return option.value !== "website";
|
||||
} else {
|
||||
return option;
|
||||
}
|
||||
});
|
||||
];
|
||||
|
||||
const toggleDropdown = (id: string) => {
|
||||
setDropdownOpenStates(new Map(dropdownOpenStates).set(id, !dropdownOpenStates.get(id)));
|
||||
@@ -128,11 +115,6 @@ export const SurveyFilters = ({
|
||||
setSurveyFilters((prev) => ({ ...prev, sortBy: option.value }));
|
||||
};
|
||||
|
||||
const handleOrientationChange = (value: string) => {
|
||||
setOrientation(value);
|
||||
localStorage.setItem(FORMBRICKS_SURVEYS_ORIENTATION_KEY_LS, value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex justify-between">
|
||||
<div className="flex space-x-2">
|
||||
@@ -193,32 +175,6 @@ export const SurveyFilters = ({
|
||||
)}
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<TooltipRenderer
|
||||
shouldRender={true}
|
||||
tooltipContent={getToolTipContent("List")}
|
||||
className="bg-slate-900 text-white">
|
||||
<div
|
||||
className={`flex h-8 w-8 items-center justify-center rounded-lg border p-1 ${
|
||||
orientation === "list" ? "bg-slate-900 text-white" : "bg-white"
|
||||
}`}
|
||||
onClick={() => handleOrientationChange("list")}>
|
||||
<Equal className="h-5 w-5" />
|
||||
</div>
|
||||
</TooltipRenderer>
|
||||
|
||||
<TooltipRenderer
|
||||
shouldRender={true}
|
||||
tooltipContent={getToolTipContent("Grid")}
|
||||
className="bg-slate-900 text-white">
|
||||
<div
|
||||
className={`flex h-8 w-8 items-center justify-center rounded-lg border p-1 ${
|
||||
orientation === "grid" ? "bg-slate-900 text-white" : "bg-white"
|
||||
}`}
|
||||
onClick={() => handleOrientationChange("grid")}>
|
||||
<Grid2X2 className="h-5 w-5" />
|
||||
</div>
|
||||
</TooltipRenderer>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
asChild
|
||||
@@ -1,20 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { getSurveysAction } from "@/app/(app)/environments/[environmentId]/surveys/actions";
|
||||
import { getFormattedFilters } from "@/app/(app)/environments/[environmentId]/surveys/lib/utils";
|
||||
import { TSurvey } from "@/app/(app)/environments/[environmentId]/surveys/types/surveys";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
FORMBRICKS_SURVEYS_FILTERS_KEY_LS,
|
||||
FORMBRICKS_SURVEYS_ORIENTATION_KEY_LS,
|
||||
} from "@formbricks/lib/localStorage";
|
||||
import { FORMBRICKS_SURVEYS_FILTERS_KEY_LS } from "@formbricks/lib/localStorage";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { wrapThrows } from "@formbricks/types/error-handlers";
|
||||
import { TProductConfigChannel } from "@formbricks/types/product";
|
||||
import { TSurvey, TSurveyFilters } from "@formbricks/types/surveys/types";
|
||||
import { Button } from "../Button";
|
||||
import { getSurveysAction } from "./actions";
|
||||
import { SurveyCard } from "./components/SurveyCard";
|
||||
import { SurveyFilters } from "./components/SurveyFilters";
|
||||
import { SurveyLoading } from "./components/SurveyLoading";
|
||||
import { getFormattedFilters } from "./utils";
|
||||
import { TSurveyFilters } from "@formbricks/types/surveys/types";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { SurveyCard } from "./SurveyCard";
|
||||
import { SurveyFilters } from "./SurveyFilters";
|
||||
import { SurveyLoading } from "./SurveyLoading";
|
||||
|
||||
interface SurveysListProps {
|
||||
environment: TEnvironment;
|
||||
@@ -52,18 +50,8 @@ export const SurveysList = ({
|
||||
|
||||
const filters = useMemo(() => getFormattedFilters(surveyFilters, userId), [surveyFilters, userId]);
|
||||
|
||||
const [orientation, setOrientation] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
const orientationFromLocalStorage = localStorage.getItem(FORMBRICKS_SURVEYS_ORIENTATION_KEY_LS);
|
||||
if (orientationFromLocalStorage) {
|
||||
setOrientation(orientationFromLocalStorage);
|
||||
} else {
|
||||
setOrientation("grid");
|
||||
localStorage.setItem(FORMBRICKS_SURVEYS_ORIENTATION_KEY_LS, "grid");
|
||||
}
|
||||
|
||||
const savedFilters = localStorage.getItem(FORMBRICKS_SURVEYS_FILTERS_KEY_LS);
|
||||
if (savedFilters) {
|
||||
const surveyParseResult = wrapThrows(() => JSON.parse(savedFilters))();
|
||||
@@ -142,59 +130,37 @@ export const SurveysList = ({
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<SurveyFilters
|
||||
orientation={orientation}
|
||||
setOrientation={setOrientation}
|
||||
surveyFilters={surveyFilters}
|
||||
setSurveyFilters={setSurveyFilters}
|
||||
currentProductChannel={currentProductChannel}
|
||||
/>
|
||||
{surveys.length > 0 ? (
|
||||
<div>
|
||||
{orientation === "list" && (
|
||||
<div className="flex-col space-y-3">
|
||||
<div className="mt-6 grid w-full grid-cols-8 place-items-center gap-3 px-6 text-sm text-slate-800">
|
||||
<div className="col-span-4 place-self-start">Name</div>
|
||||
<div className="col-span-4 grid w-full grid-cols-5 place-items-center">
|
||||
<div className="col-span-2">Created at</div>
|
||||
<div className="col-span-2">Updated at</div>
|
||||
</div>
|
||||
</div>
|
||||
{surveys.map((survey) => {
|
||||
return (
|
||||
<SurveyCard
|
||||
key={survey.id}
|
||||
survey={survey}
|
||||
environment={environment}
|
||||
otherEnvironment={otherEnvironment}
|
||||
isViewer={isViewer}
|
||||
WEBAPP_URL={WEBAPP_URL}
|
||||
orientation={orientation}
|
||||
duplicateSurvey={handleDuplicateSurvey}
|
||||
deleteSurvey={handleDeleteSurvey}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<div className="flex-col space-y-3">
|
||||
<div className="mt-6 grid w-full grid-cols-8 place-items-center gap-3 px-6 text-sm text-slate-800">
|
||||
<div className="col-span-1 place-self-start">Name</div>
|
||||
<div className="col-span-1">Status</div>
|
||||
<div className="col-span-1">Responses</div>
|
||||
<div className="col-span-1">Type</div>
|
||||
<div className="col-span-1">Created at</div>
|
||||
<div className="col-span-1">Updated at</div>
|
||||
<div className="col-span-1">Created by</div>
|
||||
</div>
|
||||
)}
|
||||
{orientation === "grid" && (
|
||||
<div className="grid grid-cols-2 place-content-stretch gap-4 lg:grid-cols-3 2xl:grid-cols-5">
|
||||
{surveys.map((survey) => {
|
||||
return (
|
||||
<SurveyCard
|
||||
key={survey.id}
|
||||
survey={survey}
|
||||
environment={environment}
|
||||
otherEnvironment={otherEnvironment}
|
||||
isViewer={isViewer}
|
||||
WEBAPP_URL={WEBAPP_URL}
|
||||
orientation={orientation}
|
||||
duplicateSurvey={handleDuplicateSurvey}
|
||||
deleteSurvey={handleDeleteSurvey}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{surveys.map((survey) => {
|
||||
return (
|
||||
<SurveyCard
|
||||
key={survey.id}
|
||||
survey={survey}
|
||||
environment={environment}
|
||||
otherEnvironment={otherEnvironment}
|
||||
isViewer={isViewer}
|
||||
WEBAPP_URL={WEBAPP_URL}
|
||||
duplicateSurvey={handleDuplicateSurvey}
|
||||
deleteSurvey={handleDeleteSurvey}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{hasMore && (
|
||||
<div className="flex justify-center py-5">
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Code, HelpCircle, Link2Icon } from "lucide-react";
|
||||
|
||||
interface SurveyTypeIndicatorProps {
|
||||
type: string;
|
||||
}
|
||||
|
||||
const surveyTypeMapping = {
|
||||
app: { icon: Code, label: "App" },
|
||||
link: { icon: Link2Icon, label: "Link" },
|
||||
};
|
||||
|
||||
export const SurveyTypeIndicator = ({ type }: SurveyTypeIndicatorProps) => {
|
||||
const { icon: Icon, label } = surveyTypeMapping[type] || { icon: HelpCircle, label: "Unknown" };
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2 text-sm text-slate-600">
|
||||
<Icon className="h-4 w-4" />
|
||||
<span>{label}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,208 @@
|
||||
import "server-only";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { cache as reactCache } from "react";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { cache } from "@formbricks/lib/cache";
|
||||
import { responseCache } from "@formbricks/lib/response/cache";
|
||||
import { surveyCache } from "@formbricks/lib/survey/cache";
|
||||
import { getInProgressSurveyCount } from "@formbricks/lib/survey/service";
|
||||
import { buildOrderByClause, buildWhereClause } from "@formbricks/lib/survey/utils";
|
||||
import { validateInputs } from "@formbricks/lib/utils/validate";
|
||||
import { ZOptionalNumber } from "@formbricks/types/common";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
import { TSurveyFilterCriteria } from "@formbricks/types/surveys/types";
|
||||
import { TSurvey } from "../types/surveys";
|
||||
|
||||
export const surveySelect: Prisma.SurveySelect = {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
name: true,
|
||||
type: true,
|
||||
creator: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
status: true,
|
||||
singleUse: true,
|
||||
environmentId: true,
|
||||
_count: {
|
||||
select: { responses: true },
|
||||
},
|
||||
};
|
||||
|
||||
export const getSurveys = reactCache(
|
||||
(
|
||||
environmentId: string,
|
||||
limit?: number,
|
||||
offset?: number,
|
||||
filterCriteria?: TSurveyFilterCriteria
|
||||
): Promise<TSurvey[]> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([environmentId, ZId], [limit, ZOptionalNumber], [offset, ZOptionalNumber]);
|
||||
|
||||
try {
|
||||
if (filterCriteria?.sortBy === "relevance") {
|
||||
// Call the sortByRelevance function
|
||||
return await getSurveysSortedByRelevance(environmentId, limit, offset ?? 0, filterCriteria);
|
||||
}
|
||||
|
||||
// Fetch surveys normally with pagination and include response count
|
||||
const surveysPrisma = await prisma.survey.findMany({
|
||||
where: {
|
||||
environmentId,
|
||||
...buildWhereClause(filterCriteria),
|
||||
},
|
||||
select: surveySelect,
|
||||
orderBy: buildOrderByClause(filterCriteria?.sortBy),
|
||||
take: limit,
|
||||
skip: offset,
|
||||
});
|
||||
|
||||
return surveysPrisma.map((survey) => {
|
||||
return {
|
||||
...survey,
|
||||
responseCount: survey._count.responses,
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
console.error(error);
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`surveyList-getSurveys-${environmentId}-${limit}-${offset}-${JSON.stringify(filterCriteria)}`],
|
||||
{
|
||||
tags: [
|
||||
surveyCache.tag.byEnvironmentId(environmentId),
|
||||
responseCache.tag.byEnvironmentId(environmentId),
|
||||
],
|
||||
}
|
||||
)()
|
||||
);
|
||||
|
||||
export const getSurveysSortedByRelevance = reactCache(
|
||||
(
|
||||
environmentId: string,
|
||||
limit?: number,
|
||||
offset?: number,
|
||||
filterCriteria?: TSurveyFilterCriteria
|
||||
): Promise<TSurvey[]> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([environmentId, ZId], [limit, ZOptionalNumber], [offset, ZOptionalNumber]);
|
||||
|
||||
try {
|
||||
let surveys: TSurvey[] = [];
|
||||
const inProgressSurveyCount = await getInProgressSurveyCount(environmentId, filterCriteria);
|
||||
|
||||
// Fetch surveys that are in progress first
|
||||
const inProgressSurveys =
|
||||
offset && offset > inProgressSurveyCount
|
||||
? []
|
||||
: await prisma.survey.findMany({
|
||||
where: {
|
||||
environmentId,
|
||||
status: "inProgress",
|
||||
...buildWhereClause(filterCriteria),
|
||||
},
|
||||
select: surveySelect,
|
||||
orderBy: buildOrderByClause("updatedAt"),
|
||||
take: limit,
|
||||
skip: offset,
|
||||
});
|
||||
|
||||
surveys = inProgressSurveys.map((survey) => {
|
||||
return {
|
||||
...survey,
|
||||
responseCount: survey._count.responses,
|
||||
};
|
||||
});
|
||||
|
||||
// Determine if additional surveys are needed
|
||||
if (offset !== undefined && limit && inProgressSurveys.length < limit) {
|
||||
const remainingLimit = limit - inProgressSurveys.length;
|
||||
const newOffset = Math.max(0, offset - inProgressSurveyCount);
|
||||
const additionalSurveys = await prisma.survey.findMany({
|
||||
where: {
|
||||
environmentId,
|
||||
status: { not: "inProgress" },
|
||||
...buildWhereClause(filterCriteria),
|
||||
},
|
||||
select: surveySelect,
|
||||
orderBy: buildOrderByClause("updatedAt"),
|
||||
take: remainingLimit,
|
||||
skip: newOffset,
|
||||
});
|
||||
|
||||
surveys = [
|
||||
...surveys,
|
||||
...additionalSurveys.map((survey) => {
|
||||
return {
|
||||
...survey,
|
||||
responseCount: survey._count.responses,
|
||||
};
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
return surveys;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
console.error(error);
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[
|
||||
`surveyList-getSurveysSortedByRelevance-${environmentId}-${limit}-${offset}-${JSON.stringify(filterCriteria)}`,
|
||||
],
|
||||
{
|
||||
tags: [
|
||||
surveyCache.tag.byEnvironmentId(environmentId),
|
||||
responseCache.tag.byEnvironmentId(environmentId),
|
||||
],
|
||||
}
|
||||
)()
|
||||
);
|
||||
|
||||
export const getSurvey = reactCache(
|
||||
(surveyId: string): Promise<TSurvey | null> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([surveyId, ZId]);
|
||||
|
||||
let surveyPrisma;
|
||||
try {
|
||||
surveyPrisma = await prisma.survey.findUnique({
|
||||
where: {
|
||||
id: surveyId,
|
||||
},
|
||||
select: surveySelect,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
console.error(error);
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!surveyPrisma) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { ...surveyPrisma, responseCount: surveyPrisma?._count.responses };
|
||||
},
|
||||
[`surveyList-getSurvey-${surveyId}`],
|
||||
{
|
||||
tags: [surveyCache.tag.byId(surveyId), responseCache.tag.bySurveyId(surveyId)],
|
||||
}
|
||||
)()
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SurveyLoading } from "@/app/(app)/environments/[environmentId]/surveys/components/SurveyLoading";
|
||||
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
|
||||
import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
import { SurveyLoading } from "@formbricks/ui/components/SurveysList/components/SurveyLoading";
|
||||
|
||||
const Loading = () => {
|
||||
return (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { SurveysList } from "@/app/(app)/environments/[environmentId]/surveys/components/SurveyList";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { Metadata } from "next";
|
||||
import { getServerSession } from "next-auth";
|
||||
@@ -14,7 +15,6 @@ import { TTemplateRole } from "@formbricks/types/templates";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
|
||||
import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
import { SurveysList } from "@formbricks/ui/components/SurveysList";
|
||||
import { TemplateList } from "@formbricks/ui/components/TemplateList";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { z } from "zod";
|
||||
import { ZSurveyStatus } from "@formbricks/types/surveys/types";
|
||||
|
||||
export const ZSurvey = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
environmentId: z.string(),
|
||||
type: z.enum(["link", "app", "website", "web"]), //we can replace this with ZSurveyType after we remove "web" from schema
|
||||
status: ZSurveyStatus,
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
responseCount: z.number(),
|
||||
creator: z
|
||||
.object({
|
||||
name: z.string(),
|
||||
})
|
||||
.nullable(),
|
||||
singleUse: z
|
||||
.object({
|
||||
enabled: z.boolean(),
|
||||
isEncrypted: z.boolean(),
|
||||
})
|
||||
.nullable(),
|
||||
});
|
||||
|
||||
export type TSurvey = z.infer<typeof ZSurvey>;
|
||||
|
||||
export const ZSurveyCopyFormValidation = z.object({
|
||||
products: z.array(
|
||||
z.object({
|
||||
product: z.string(),
|
||||
environments: z.array(z.string()),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export type TSurveyCopyFormData = z.infer<typeof ZSurveyCopyFormValidation>;
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import { type TFormbricksApp } from "@formbricks/js-core";
|
||||
import { loadFormbricksToProxy } from "./lib/load-formbricks";
|
||||
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export const FORMBRICKS_SURVEYS_ORIENTATION_KEY_LS = "formbricks-surveys-orientation";
|
||||
export const FORMBRICKS_SURVEYS_FILTERS_KEY_LS = "formbricks-surveys-filters";
|
||||
export const FORMBRICKS_ENVIRONMENT_ID_LS = "formbricks-environment-id";
|
||||
|
||||
@@ -277,74 +277,6 @@ export const getSurveysByActionClassId = reactCache(
|
||||
)()
|
||||
);
|
||||
|
||||
export const getSurveysSortedByRelevance = reactCache(
|
||||
(
|
||||
environmentId: string,
|
||||
limit?: number,
|
||||
offset?: number,
|
||||
filterCriteria?: TSurveyFilterCriteria
|
||||
): Promise<TSurvey[]> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([environmentId, ZId], [limit, ZOptionalNumber], [offset, ZOptionalNumber]);
|
||||
|
||||
try {
|
||||
let surveys: TSurvey[] = [];
|
||||
const inProgressSurveyCount = await getInProgressSurveyCount(environmentId, filterCriteria);
|
||||
|
||||
// Fetch surveys that are in progress first
|
||||
const inProgressSurveys =
|
||||
offset && offset > inProgressSurveyCount
|
||||
? []
|
||||
: await prisma.survey.findMany({
|
||||
where: {
|
||||
environmentId,
|
||||
status: "inProgress",
|
||||
...buildWhereClause(filterCriteria),
|
||||
},
|
||||
select: selectSurvey,
|
||||
orderBy: buildOrderByClause("updatedAt"),
|
||||
take: limit,
|
||||
skip: offset,
|
||||
});
|
||||
|
||||
surveys = inProgressSurveys.map(transformPrismaSurvey);
|
||||
|
||||
// Determine if additional surveys are needed
|
||||
if (offset !== undefined && limit && inProgressSurveys.length < limit) {
|
||||
const remainingLimit = limit - inProgressSurveys.length;
|
||||
const newOffset = Math.max(0, offset - inProgressSurveyCount);
|
||||
const additionalSurveys = await prisma.survey.findMany({
|
||||
where: {
|
||||
environmentId,
|
||||
status: { not: "inProgress" },
|
||||
...buildWhereClause(filterCriteria),
|
||||
},
|
||||
select: selectSurvey,
|
||||
orderBy: buildOrderByClause("updatedAt"),
|
||||
take: remainingLimit,
|
||||
skip: newOffset,
|
||||
});
|
||||
|
||||
surveys = [...surveys, ...additionalSurveys.map(transformPrismaSurvey)];
|
||||
}
|
||||
|
||||
return surveys;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
console.error(error);
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`getSurveysSortedByRelevance-${environmentId}-${limit}-${offset}-${JSON.stringify(filterCriteria)}`],
|
||||
{
|
||||
tags: [surveyCache.tag.byEnvironmentId(environmentId)],
|
||||
}
|
||||
)()
|
||||
);
|
||||
|
||||
export const getSurveys = reactCache(
|
||||
(
|
||||
environmentId: string,
|
||||
@@ -357,12 +289,6 @@ export const getSurveys = reactCache(
|
||||
validateInputs([environmentId, ZId], [limit, ZOptionalNumber], [offset, ZOptionalNumber]);
|
||||
|
||||
try {
|
||||
if (filterCriteria?.sortBy === "relevance") {
|
||||
// Call the sortByRelevance function
|
||||
return await getSurveysSortedByRelevance(environmentId, limit, offset ?? 0, filterCriteria);
|
||||
}
|
||||
|
||||
// Fetch surveys normally with pagination
|
||||
const surveysPrisma = await prisma.survey.findMany({
|
||||
where: {
|
||||
environmentId,
|
||||
|
||||
@@ -2477,14 +2477,3 @@ export const ZSurveyRecallItem = z.object({
|
||||
});
|
||||
|
||||
export type TSurveyRecallItem = z.infer<typeof ZSurveyRecallItem>;
|
||||
|
||||
export const ZSurveyCopyFormValidation = z.object({
|
||||
products: z.array(
|
||||
z.object({
|
||||
product: z.string(),
|
||||
environments: z.array(z.string()),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export type TSurveyCopyFormData = z.infer<typeof ZSurveyCopyFormValidation>;
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
import { Code, Link2Icon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { getFormattedErrorMessage } from "@formbricks/lib/actionClient/helper";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { convertDateString, timeSince } from "@formbricks/lib/time";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TSurvey, TSurveyType } from "@formbricks/types/surveys/types";
|
||||
import { SurveyStatusIndicator } from "../../SurveyStatusIndicator";
|
||||
import { generateSingleUseIdAction } from "../actions";
|
||||
import { SurveyDropDownMenu } from "./SurveyDropdownMenu";
|
||||
|
||||
interface SurveyCardProps {
|
||||
survey: TSurvey;
|
||||
environment: TEnvironment;
|
||||
otherEnvironment: TEnvironment;
|
||||
isViewer: boolean;
|
||||
WEBAPP_URL: string;
|
||||
orientation: string;
|
||||
duplicateSurvey: (survey: TSurvey) => void;
|
||||
deleteSurvey: (surveyId: string) => void;
|
||||
}
|
||||
export const SurveyCard = ({
|
||||
survey,
|
||||
environment,
|
||||
otherEnvironment,
|
||||
isViewer,
|
||||
WEBAPP_URL,
|
||||
orientation,
|
||||
deleteSurvey,
|
||||
duplicateSurvey,
|
||||
}: SurveyCardProps) => {
|
||||
const isSurveyCreationDeletionDisabled = isViewer;
|
||||
|
||||
const surveyStatusLabel = useMemo(() => {
|
||||
if (survey.status === "inProgress") return "In Progress";
|
||||
else if (survey.status === "scheduled") return "Scheduled";
|
||||
else if (survey.status === "completed") return "Completed";
|
||||
else if (survey.status === "draft") return "Draft";
|
||||
else if (survey.status === "paused") return "Paused";
|
||||
}, [survey]);
|
||||
|
||||
const [singleUseId, setSingleUseId] = useState<string | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSingleUseId = async () => {
|
||||
if (survey.singleUse?.enabled) {
|
||||
const generateSingleUseIdResponse = await generateSingleUseIdAction({
|
||||
surveyId: survey.id,
|
||||
isEncrypted: survey.singleUse?.isEncrypted ? true : false,
|
||||
});
|
||||
if (generateSingleUseIdResponse?.data) {
|
||||
setSingleUseId(generateSingleUseIdResponse.data);
|
||||
} else {
|
||||
const errorMessage = getFormattedErrorMessage(generateSingleUseIdResponse);
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
} else {
|
||||
setSingleUseId(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
fetchSingleUseId();
|
||||
}, [survey]);
|
||||
|
||||
const linkHref = useMemo(() => {
|
||||
return survey.status === "draft"
|
||||
? `/environments/${environment.id}/surveys/${survey.id}/edit`
|
||||
: `/environments/${environment.id}/surveys/${survey.id}/summary`;
|
||||
}, [survey.status, survey.id, environment.id]);
|
||||
|
||||
const SurveyTypeIndicator = ({ type }: { type: TSurveyType }) => (
|
||||
<div className="flex items-center space-x-2 text-sm text-slate-600">
|
||||
{type === "app" && (
|
||||
<>
|
||||
<Code className="h-4 w-4" />
|
||||
<span>App</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
{type === "link" && (
|
||||
<>
|
||||
<Link2Icon className="h-4 w-4" />
|
||||
<span> Link</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderGridContent = () => {
|
||||
return (
|
||||
<Link
|
||||
href={linkHref}
|
||||
key={survey.id}
|
||||
className="relative col-span-1 flex h-44 flex-col justify-between rounded-xl border border-slate-200 bg-white p-4 shadow-sm transition-all ease-in-out hover:scale-105">
|
||||
<div className="flex justify-between">
|
||||
<SurveyTypeIndicator type={survey.type} />
|
||||
<SurveyDropDownMenu
|
||||
survey={survey}
|
||||
key={`surveys-${survey.id}`}
|
||||
environmentId={environment.id}
|
||||
environment={environment}
|
||||
otherEnvironment={otherEnvironment!}
|
||||
webAppUrl={WEBAPP_URL}
|
||||
singleUseId={singleUseId}
|
||||
isSurveyCreationDeletionDisabled={isSurveyCreationDeletionDisabled}
|
||||
duplicateSurvey={duplicateSurvey}
|
||||
deleteSurvey={deleteSurvey}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-md font-medium text-slate-900">{survey.name}</div>
|
||||
<div
|
||||
className={cn(
|
||||
"mt-3 flex w-fit items-center gap-2 rounded-full py-1 pl-1 pr-2 text-xs text-slate-800",
|
||||
surveyStatusLabel === "Scheduled" && "bg-slate-200",
|
||||
surveyStatusLabel === "In Progress" && "bg-emerald-50",
|
||||
surveyStatusLabel === "Completed" && "bg-slate-200",
|
||||
surveyStatusLabel === "Draft" && "bg-slate-100",
|
||||
surveyStatusLabel === "Paused" && "bg-slate-100"
|
||||
)}>
|
||||
<SurveyStatusIndicator status={survey.status} /> {surveyStatusLabel}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const renderListContent = () => {
|
||||
return (
|
||||
<Link
|
||||
href={linkHref}
|
||||
key={survey.id}
|
||||
className="relative grid w-full grid-cols-8 place-items-center gap-3 rounded-xl border border-slate-200 bg-white p-4 shadow-sm transition-all ease-in-out hover:scale-[101%]">
|
||||
<div className="col-span-2 flex max-w-full items-center justify-self-start text-sm font-medium text-slate-900">
|
||||
<div className="w-full truncate">{survey.name}</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"flex w-fit items-center gap-2 rounded-full py-1 pl-1 pr-2 text-sm text-slate-800",
|
||||
surveyStatusLabel === "Scheduled" && "bg-slate-200",
|
||||
surveyStatusLabel === "In Progress" && "bg-emerald-50",
|
||||
surveyStatusLabel === "Completed" && "bg-slate-200",
|
||||
surveyStatusLabel === "Draft" && "bg-slate-100",
|
||||
surveyStatusLabel === "Paused" && "bg-slate-100"
|
||||
)}>
|
||||
<SurveyStatusIndicator status={survey.status} /> {surveyStatusLabel}{" "}
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<SurveyTypeIndicator type={survey.type} />
|
||||
</div>
|
||||
|
||||
<div className="col-span-4 grid w-full grid-cols-5 place-items-center">
|
||||
<div className="col-span-2 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-600">
|
||||
{convertDateString(survey.createdAt.toString())}
|
||||
</div>
|
||||
<div className="col-span-2 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-600">
|
||||
{timeSince(survey.updatedAt.toString())}
|
||||
</div>
|
||||
<div className="place-self-end">
|
||||
<SurveyDropDownMenu
|
||||
survey={survey}
|
||||
key={`surveys-${survey.id}`}
|
||||
environmentId={environment.id}
|
||||
environment={environment}
|
||||
otherEnvironment={otherEnvironment!}
|
||||
webAppUrl={WEBAPP_URL}
|
||||
singleUseId={singleUseId}
|
||||
isSurveyCreationDeletionDisabled={isSurveyCreationDeletionDisabled}
|
||||
duplicateSurvey={duplicateSurvey}
|
||||
deleteSurvey={deleteSurvey}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
if (orientation === "grid") {
|
||||
return renderGridContent();
|
||||
} else return renderListContent();
|
||||
};
|
||||
Reference in New Issue
Block a user