This commit is contained in:
Johannes
2023-06-13 11:14:01 +02:00
parent c1b1f6cacb
commit 424a2f1a69
5 changed files with 68 additions and 54 deletions

View File

@@ -1,15 +1,16 @@
"use client";
import { timeSince } from "@formbricks/lib/time";
import { EyeSlashIcon, PlusIcon, EyeIcon } from "@heroicons/react/24/solid";
import { useState } from "react";
import toast from "react-hot-toast";
import { useResponses } from "@/lib/responses/responses";
import { Button } from "@formbricks/ui";
import clsx from "clsx";
import { addResponseNote } from "@/lib/responseNotes/responsesNotes";
import { FormEvent } from "react";
import { OpenTextSummaryProps } from "@/app/environments/[environmentId]/surveys/[surveyId]/responses/SingleResponse";
import { addResponseNote } from "@/lib/responseNotes/responsesNotes";
import { useResponses } from "@/lib/responses/responses";
import { timeSince } from "@formbricks/lib/time";
import { Button } from "@formbricks/ui";
import { PlusIcon } from "@heroicons/react/24/outline";
import { MinusIcon } from "@heroicons/react/24/solid";
import clsx from "clsx";
import { Maximize2Icon } from "lucide-react";
import { FormEvent, useEffect, useRef, useState } from "react";
import toast from "react-hot-toast";
export default function ResponseNote({
data,
@@ -22,6 +23,8 @@ export default function ResponseNote({
const [isCreatingNote, setIsCreatingNote] = useState(false);
const { mutateResponses } = useResponses(environmentId, surveyId);
const responseNotes = data?.responseNotes;
const divRef = useRef<HTMLDivElement>(null);
const handleNoteSubmission = async (e: FormEvent) => {
e.preventDefault();
setIsCreatingNote(true);
@@ -36,12 +39,18 @@ export default function ResponseNote({
}
};
useEffect(() => {
if (divRef.current) {
divRef.current.scrollTop = divRef.current.scrollHeight;
}
}, [responseNotes]);
return (
<div
className={clsx(
"absolute w-1/5 cursor-pointer rounded-lg border border-slate-200 shadow-sm transition-all",
!isOpen && responseNotes.length && "bg-white",
!isOpen && !responseNotes.length && "bg-slate-50",
"absolute w-1/4 rounded-lg border border-slate-200 shadow-sm transition-all",
!isOpen && responseNotes.length && "group/hint cursor-pointer bg-white hover:-right-3",
!isOpen && !responseNotes.length && "cursor-pointer bg-slate-50",
isOpen
? "-right-5 top-0 h-full w-1/5 bg-white"
: responseNotes.length
@@ -56,7 +65,7 @@ export default function ResponseNote({
<div
className={clsx(
"space-y-2 rounded-t-lg px-2 pb-2 pt-2",
responseNotes.length ? "flex h-16 items-center justify-end bg-amber-50" : "bg-slate-200"
responseNotes.length ? "flex h-12 items-center justify-end bg-amber-50" : "bg-slate-200"
)}>
{!responseNotes.length ? (
<div className="flex items-center justify-end">
@@ -66,43 +75,43 @@ export default function ResponseNote({
</div>
) : (
<div className="float-left mr-1.5">
<EyeIcon className="h-4 w-4 text-amber-400" />
<Maximize2Icon className="h-4 w-4 text-amber-500 hover:text-amber-600 group-hover/hint:scale-110" />
</div>
)}
</div>
{!responseNotes.length ? (
<div className="flex flex-1 items-center justify-end pr-2">
<button className="h-6 w-6 rounded-full bg-slate-600">
<span>
<PlusIcon className="text-white" />
</span>
</button>
<div className="flex flex-1 items-center justify-end pr-3">
<span>
<PlusIcon className=" h-5 w-5 text-slate-400" />
</span>
</div>
) : null}
</div>
) : (
<div className="relative flex h-full flex-col">
<div className="rounded-t-lg bg-amber-50 px-4 pb-5 pt-6">
<div className="rounded-t-lg bg-amber-50 px-4 pb-3 pt-4">
<div className="flex items-center justify-between">
<div className="group flex items-center">
<h3 className="pb-1 text-sm text-slate-600">Note</h3>
<h3 className="pb-1 text-sm text-slate-500">Note</h3>
</div>
<div
className="h-4 w-4 cursor-pointer"
<button
className="h-6 w-6 cursor-pointer"
onClick={() => {
setIsOpen(!isOpen);
}}>
<EyeSlashIcon className="text-amber-400" />
</div>
<MinusIcon className="h-5 w-5 text-amber-500 hover:text-amber-600" />
</button>
</div>
</div>
<div className="flex-1 overflow-auto px-4 pt-2">
<div className="flex-1 overflow-auto px-4 pt-2" ref={divRef}>
{responseNotes.map((note) => (
<div className="mb-2" key={note.id}>
<span className="block text-xs text-slate-500">
{note.user.name} wrote{" "}
<time className="text-slate-500" dateTime={timeSince(data.updatedAt)}>
({timeSince(note.updatedAt)})
<div className="mb-3" key={note.id}>
<span className="block font-semibold text-slate-700">
{note.user.name}
<time
className="ml-2 text-xs font-normal text-slate-500"
dateTime={timeSince(data.updatedAt)}>
{timeSince(note.updatedAt)}
</time>
</span>
<span className="block text-slate-700">{note.text}</span>
@@ -112,7 +121,7 @@ export default function ResponseNote({
<div className="h-[120px]">
<div
className={clsx(
"absolute bottom-0 w-full px-4 pb-2",
"absolute bottom-0 w-full px-3 pb-3",
!responseNotes.length && "absolute bottom-0"
)}>
<form onSubmit={handleNoteSubmission}>
@@ -131,12 +140,8 @@ export default function ResponseNote({
}}
required></textarea>
</div>
<div className="mt-4 flex w-full justify-end">
<Button
className="bg-slate-600 hover:bg-slate-400"
size="sm"
type="submit"
loading={isCreatingNote}>
<div className="mt-2 flex w-full justify-end">
<Button variant="darkCTA" size="sm" type="submit" loading={isCreatingNote}>
Send
</Button>
</div>

View File

@@ -11,7 +11,7 @@ import toast from "react-hot-toast";
import { RatingResponse } from "../RatingResponse";
import { deleteSubmission, useResponses } from "@/lib/responses/responses";
import clsx from "clsx";
import ResponseNote from "@/app/environments/[environmentId]/surveys/[surveyId]/responses/ResponseNote";
import ResponseNote from "./ResponseNote";
export interface OpenTextSummaryProps {
data: {
@@ -25,15 +25,15 @@ export interface OpenTextSummaryProps {
environmentId: string;
attributes: [];
};
responseNote: {
responseNotes: {
updatedAt: string;
createdAt: string;
id: string;
text: string;
user: {
name: string;
}
}[]
};
}[];
value: string;
updatedAt: string;
finished: boolean;
@@ -58,11 +58,11 @@ function findEmail(person) {
export default function SingleResponse({ data, environmentId, surveyId }: OpenTextSummaryProps) {
const email = data.person && findEmail(data.person);
const displayIdentifier = email || data.personId;
const responseNotes = data?.responseNote;
const responseNotes = data?.responseNotes;
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const { mutateResponses } = useResponses(environmentId, surveyId);
const [isDeleting, setIsDeleting] = useState(false);
const [isOpen, setIsOpen] = useState(false)
const [isOpen, setIsOpen] = useState(false);
const handleDeleteSubmission = async () => {
setIsDeleting(true);
@@ -74,11 +74,12 @@ export default function SingleResponse({ data, environmentId, surveyId }: OpenTe
};
return (
<div className={clsx("relative group", isOpen && "min-h-[300px]")}>
<div className={clsx(
"my-6 rounded-lg border transition-all border-slate-200 bg-slate-50 shadow-sm z-10 relative",
isOpen ? "w-4/5" : responseNotes.length ? "w-[96.5%]" : "w-full group-hover:w-[96.5%]"
)}>
<div className={clsx("group relative", isOpen && "min-h-[300px]")}>
<div
className={clsx(
"relative z-10 my-6 rounded-lg border border-slate-200 bg-slate-50 shadow-sm transition-all",
isOpen ? "w-3/4" : responseNotes.length ? "w-[96.5%]" : "w-full group-hover:w-[96.5%]"
)}>
<div className="space-y-2 px-6 pb-5 pt-6">
<div className="flex items-center justify-between">
{data.personId ? (
@@ -128,7 +129,9 @@ export default function SingleResponse({ data, environmentId, surveyId }: OpenTe
<p className="ph-no-capture my-1 font-semibold text-slate-700">{response.answer}</p>
)
) : (
<p className="ph-no-capture my-1 font-semibold text-slate-700">{response.answer.join(", ")}</p>
<p className="ph-no-capture my-1 font-semibold text-slate-700">
{response.answer.join(", ")}
</p>
)}
</div>
))}
@@ -141,7 +144,13 @@ export default function SingleResponse({ data, environmentId, surveyId }: OpenTe
isDeleting={isDeleting}
/>
</div>
<ResponseNote data={data} environmentId={environmentId} surveyId={surveyId} isOpen={isOpen} setIsOpen={setIsOpen} />
<ResponseNote
data={data}
environmentId={environmentId}
surveyId={surveyId}
isOpen={isOpen}
setIsOpen={setIsOpen}
/>
</div>
);
}

View File

@@ -61,7 +61,7 @@ export default function MultipleChoiceMultiQuestion({
<div className="mt-4">
<fieldset>
<legend className="sr-only">Options</legend>
<div className="xs:max-h-[40vh] relative max-h-[60vh] space-y-2 overflow-y-auto rounded-md py-0.5 pr-2">
<div className="xs:max-h-[41vh] relative max-h-[60vh] space-y-2 overflow-y-auto rounded-md py-0.5 pr-2">
{question.choices &&
question.choices.map((choice) => (
<>

View File

@@ -42,7 +42,7 @@ export default function MultipleChoiceSingleQuestion({
<div className="mt-4">
<fieldset>
<legend className="sr-only">Options</legend>
<div className="xs:max-h-[40vh] relative max-h-[60vh] space-y-2 overflow-y-auto rounded-md py-0.5 pr-2">
<div className="xs:max-h-[41vh] relative max-h-[60vh] space-y-2 overflow-y-auto rounded-md py-0.5 pr-2">
{question.choices &&
question.choices.map((choice, idx) => (
<label