Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/Create multiquestions surveys #182

Merged
merged 2 commits into from
Jun 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
import { PlusCircleIcon } from '@heroicons/react/outline';
import Button, {
ButtonSize,
ButtonVariant,
} from 'shared/components/Button/Button';
import NewQuestionModal from 'features/surveys/components/NewQuestionModal/NewQuestionModal';
import useModal from 'features/surveys/hooks/useModal';
import { Question } from 'features/surveys/managers/createSurveyManager';

export const AddQuestionButton = () => {
interface AddQuestionButtonProps {
onClick: (newQuestion: Question) => void;
}

export const AddQuestionButton = ({ onClick }: AddQuestionButtonProps) => {
const { closeModal, isModalOpen, openModal } = useModal();
return (
<button className="my-4 flex h-[60px] w-full cursor-pointer items-center justify-center rounded-md border border-gray-300 text-gray-400 duration-300 hover:scale-95">
Add question <PlusCircleIcon className="m-2 h-6 w-6" />
</button>
<>
<Button
onClick={openModal}
variant={ButtonVariant.OUTLINE}
sizeType={ButtonSize.FULL}
icon={<PlusCircleIcon className="h-5 w-5" />}
>
Add Question
</Button>

<NewQuestionModal
onSuccess={onClick}
closeModal={closeModal}
isOpened={isModalOpen}
/>
</>
);
};
39 changes: 11 additions & 28 deletions src/features/surveys/components/AnswerHeader/AnswerHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,24 @@
import clsx from 'clsx';
import { BarChartData } from 'features/surveys/components/BarChart/BarChart';
import BarChart from 'features/surveys/components/BarChart/BarChart';
import DataCard from 'features/surveys/components/DataCard/DataCard';
import useTranslation from 'next-translate/useTranslation';
import { formatDate } from 'shared/utilities/convertTime';

interface AnswerHeaderProps {
totalVotes: number;
createDate: string;
chartData: BarChartData[];
createDate: string | Date;
}

function AnswerHeader({
totalVotes,
createDate,
chartData,
}: AnswerHeaderProps) {
function AnswerHeader({ totalVotes, createDate }: AnswerHeaderProps) {
const { t } = useTranslation('surveyAnswer');
return (
<div className="flex flex-col-reverse items-center justify-center md:flex-row">
{chartData.length ? <BarChart data={chartData} /> : null}

<div
className={clsx(
'mb-12 mt-6 flex w-full flex-col items-center justify-center md:my-0 md:ml-6 md:w-auto md:-translate-y-4',
!chartData.length ? 'md:flex-row' : ''
)}
>
<DataCard
title={t('createDateInformation')}
value={formatDate(createDate)}
/>
<DataCard
title={t('totalVotesInformation')}
value={totalVotes.toString()}
/>
</div>
<div className={'my-6 flex flex-col gap-1 md:flex-row'}>
<DataCard
title={t('createDateInformation')}
value={formatDate(createDate)}
/>
<DataCard
title={t('totalVotesInformation')}
value={totalVotes.toString()}
/>
</div>
);
}
Expand Down
19 changes: 8 additions & 11 deletions src/features/surveys/components/AnswerTableRow/AnswerTableRow.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import Emoji from 'features/surveys/components/Emoji/Emoji';
import { formatDate } from 'shared/utilities/convertTime';
import { MappedAnswerData } from 'types/MappedAnswerData';

interface AnswerTableRowProps {
time: string;
selectedIcon: string;
text: string;
answer: MappedAnswerData;
}

function AnswerTableRow({ time, selectedIcon, text }: AnswerTableRowProps) {
function AnswerTableRow({ answer }: AnswerTableRowProps) {
return (
<div className="my-2 grid grid-cols-8 rounded-md bg-white p-3 shadow-sm">
<div className="col-span-3 hidden text-sm xsm:block">{time}</div>
<div className="col-span-2 ml-[4px] mr-2 mt-[1px] flex justify-center xsm:col-span-1 xsm:justify-start">
<Emoji shortcodes={selectedIcon} size={22} />
</div>
<div className="col-span-6 break-words text-left xsm:col-span-4">
{text || '-'}
<div className="col-span-2 text-sm">{formatDate(answer.date)}</div>

<div className="col-span-6 break-words text-left text-sm">
{answer.answer || '-'}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
import ButtonsAnswersComponent from 'features/surveys/components/AnswersComponent/ButtonsAnswersComponent';
import { QuestionType } from '@prisma/client';
import ListAnswersComponent from 'features/surveys/components/AnswersComponent/ListAnswersComponent';

export enum AnswerType {
BUTTONS = 'buttons',
LIST = 'list',
}
import TextAnswersComponent from 'features/surveys/components/AnswersComponent/TextAnswersComponent';

interface AnswersComponentFactoryProps {
type: AnswerType;
icons: string[];
selectedIcon: string;
handleIconClick: (icon: string) => void;
showEmojiError: boolean;
answer: string;
handleInputAnswer: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
type: QuestionType;
options: string[];
handleAnswerChange: (answer: string, questionId: string) => void;
answer?: string;
questionId: string;
question: string;
isSubmitted: boolean;
}
export const AnswersComponentFactory = (
props: AnswersComponentFactoryProps
) => {
const { type } = props;

switch (type) {
case AnswerType.BUTTONS:
return <ButtonsAnswersComponent {...props} />;
case AnswerType.LIST:
return <ListAnswersComponent {...props} />;
default:
return null;
}
return (
<div className="mb-3 rounded-md border bg-white/50 px-6 py-4 shadow">
<h2 className="mb-5 text-lg font-semibold">{props.question}</h2>
{type === QuestionType.EMOJI && <ListAnswersComponent {...props} />}
{type === QuestionType.INPUT && <TextAnswersComponent {...props} />}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,6 @@ export default function ButtonsAnswersComponent({
{showEmojiError && (
<div className="mt-2 text-red-500">{t('choseEmojiBeforeSend')}</div>
)}
{/* <div className="mt-8">
<textarea
className="h-52 w-full resize-none rounded-lg p-4 shadow focus:outline-none"
placeholder={t('sendFeedbackTellUsMore')}
value={answer}
onChange={handleInputAnswer}
></textarea>
</div> */}
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,49 +1,44 @@
import React from 'react';
import EmojiListItem from 'features/surveys/components/AnswersComponent/EmojiListItem/EmojiListItem';
import useTranslation from 'next-translate/useTranslation';
import { QuestionType } from '@prisma/client';

interface ListAnswersComponentProps {
icons: string[];
selectedIcon: string;
handleIconClick: (icon: string) => void;
showEmojiError: boolean;
answer: string;
handleInputAnswer: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
type: QuestionType;
options: string[];
handleAnswerChange: (answer: string, questionId: string) => void;
answer?: string;
questionId: string;
isSubmitted: boolean;
}

export default function ListAnswersComponent({
icons,
type,
options,
handleAnswerChange,
answer,
handleIconClick,
handleInputAnswer,
selectedIcon,
showEmojiError,
questionId,
isSubmitted,
}: ListAnswersComponentProps) {
const { t } = useTranslation('survey');
const onAnswerChange = (answer: string) => {
handleAnswerChange(answer, questionId);
};

return (
<div className="rounded-xl border bg-white/50 px-6 py-4 shadow">
<div className="mt-1 flex flex-wrap justify-center gap-y-2">
{icons.map((icon, idx) => (
<>
<div className="mb-2 flex flex-wrap justify-center gap-y-2">
{options.map((icon, idx) => (
<EmojiListItem
icon={icon}
selected={selectedIcon === icon}
isAnySelected={selectedIcon !== ''}
selected={answer === icon}
isAnySelected={!!answer}
key={idx}
onClick={handleIconClick}
onClick={onAnswerChange}
/>
))}
</div>
{showEmojiError && (
<div className="mt-5 text-red-500">{t('choseEmojiBeforeSend')}</div>
{isSubmitted && !answer && (
<p className="mt-4 text-sm text-red-500">Please select an answer</p>
)}
{/* <div className="mt-6">
<textarea
className="h-40 w-full resize-none rounded-lg bg-zinc-100 p-4 shadow focus:outline-none"
placeholder={t('sendFeedbackTellUsMore')}
value={answer}
onChange={handleInputAnswer}
></textarea>
</div> */}
</div>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import clsx from 'clsx';
import React, { ChangeEvent } from 'react';
import Input from 'shared/components/Input/Input';
import { MAX_ANSWER_LENGTH } from 'shared/constants/surveysConfig';

interface TextAnswersComponentProps {
handleAnswerChange: (answer: string, questionId: string) => void;
answer?: string;
questionId: string;
isSubmitted: boolean;
}

export default function TextAnswersComponent({
handleAnswerChange,
answer,
questionId,
isSubmitted,
}: TextAnswersComponentProps) {
const onAnswerChange = (e: ChangeEvent<HTMLInputElement>) => {
e.preventDefault();
handleAnswerChange(e.target.value, questionId);
};

const getAnswerError = () => {
if ((!answer || answer?.trim() === '') && isSubmitted) {
return 'Answer is required';
}
return undefined;
};

return (
<div>
<Input
value={answer}
onInput={onAnswerChange}
placeholder="Answer..."
error={getAnswerError()}
absoluteError
className={clsx(getAnswerError() && 'mb-6')}
maxLength={MAX_ANSWER_LENGTH}
></Input>
</div>
);
}
2 changes: 1 addition & 1 deletion src/features/surveys/components/DataCard/DataCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ interface DataCardProps {

export default function DataCard({ title, value }: DataCardProps) {
return (
<div className="m-1 w-full rounded-lg border border-zinc-200 bg-white px-4 py-2 shadow-md sm:w-1/2 md:w-[220px]">
<div className="w-full rounded-lg border bg-white/50 px-4 py-2 shadow-sm">
<h3 className="mb-1 font-semibold">{title}</h3>
{value}
</div>
Expand Down
29 changes: 16 additions & 13 deletions src/features/surveys/components/EmojiPicker/EmojiPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ interface EmojiPickerProps {
index?: number;
pickedEmoji?: string;
addEmoji?: boolean;
onEmotePick?: (idx: number, newValue: string) => void;
onEmoteAdd?: (newValue: string) => void;
onEmoteRemove?: (idx: number) => void;
onEmotePick?: (idx: number, newValue: string, questionIndex: number) => void;
onEmoteAdd?: (newValue: string, questionIndex: number) => void;
onEmoteRemove?: (idx: number, questionIndex: number) => void;
questionIndex: number;
}

function EmojiPicker({
Expand All @@ -23,37 +24,36 @@ function EmojiPicker({
onEmotePick,
onEmoteAdd,
onEmoteRemove,
questionIndex,
}: EmojiPickerProps) {
const [displayPicker, setDisplayPicker] = useState(false);

const onEmojiClick = (emojiObject: EmojiObject) => {
onEmotePick?.(index, emojiObject.shortcodes);
onEmotePick?.(index, emojiObject.shortcodes, questionIndex);
setDisplayPicker(false);
};

const onEmojiClickAdd = (emojiObject: EmojiObject) => {
onEmoteAdd?.(emojiObject.shortcodes);
onEmoteAdd?.(emojiObject.shortcodes, questionIndex);
setDisplayPicker(false);
};

return (
<div>
<button
type="button"
className="label-text flex min-h-[56px] w-16 items-center justify-center rounded-lg bg-white p-3 text-3xl shadow transition hover:scale-95"
className="label-text flex min-h-[57px] w-16 items-center justify-center rounded-lg bg-white p-3 shadow transition hover:scale-95"
onClick={() => setDisplayPicker(!displayPicker)}
>
{!addEmoji ? (
<Emoji shortcodes={pickedEmoji || ''} />
) : (
<div className="flex h-[34px] w-[32px] items-center">
<PlusSmIcon />
</div>
<PlusSmIcon className="w-[32px]" />
)}
</button>
{onEmoteRemove && (
<Button
onClick={() => onEmoteRemove(index)}
onClick={() => onEmoteRemove(index, questionIndex)}
className="mt-1 w-[64px]"
variant={ButtonVariant.DANGER}
icon={<TrashIcon className="h-4 w-4" />}
Expand All @@ -71,9 +71,10 @@ function EmojiPicker({
searchPosition="none"
dynamicWidth={true}
skinTonePosition="none"
emojiSize={30}
emojiButtonSize={40}
emojiButtonRadius={'8px'}
emojiSize={28}
emojiButtonSize={38}
emojiButtonRadius={'6px'}
theme="light"
categories={[
'frequent',
'giffs',
Expand All @@ -83,6 +84,8 @@ function EmojiPicker({
'activity',
'places',
'flags',
'objects',
'symbols',
]}
/>
}
Expand Down
Loading