Просмотр исходного кода

refs 112702: differentiate modals by questionnaire order

Futa Arai 3 лет назад
Родитель
Сommit
c560321f9e

+ 5 - 5
.devcontainer/Dockerfile

@@ -3,7 +3,7 @@
 # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
 #-------------------------------------------------------------------------------------------------------------
 
-FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-16
+FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-16-bullseye
 
 # The node image includes a non-root user with sudo access. Use the
 # "remoteUser" property in devcontainer.json to use it. On Linux, update
@@ -33,13 +33,13 @@ RUN chown -R $USER_UID:$USER_GID /home/$USERNAME /workspace;
 ENV DEBIAN_FRONTEND=noninteractive
 
 # Prepare to install Chrome for VRT
-RUN sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
-RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
+# RUN sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
+# RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
 
 RUN apt-get update \
    && apt-get -y install --no-install-recommends git-lfs \
       # Chrome
-      google-chrome-stable \
+      # google-chrome-stable \
       # for Cypress
       libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb fonts-noto-cjk \
 
@@ -50,4 +50,4 @@ RUN apt-get update \
 ENV DEBIAN_FRONTEND=dialog
 
 # Uncomment to default to non-root user
-# USER $USER_UID
+# USER $USER_UID

+ 17 - 0
packages/app/src/components/PopUps.tsx

@@ -0,0 +1,17 @@
+import { useSWRxQuestionnaireOrders } from '~/stores/questionnaire';
+
+import QuestionnaireToast from './Questionnaire/QuestionnaireToast';
+
+const PopUps = (): JSX.Element => {
+  const { data: questionnaireOrders } = useSWRxQuestionnaireOrders();
+
+  return <div style={{
+    position: 'fixed', bottom: 20, right: 20, width: 280,
+  }}>
+    {questionnaireOrders?.map((questionnaireOrder) => {
+      return <QuestionnaireToast questionnaireOrder={questionnaireOrder} key={questionnaireOrder._id}/>;
+    })}
+  </div>;
+};
+
+export default PopUps;

+ 7 - 7
packages/app/src/components/Questionnaire/Question.tsx

@@ -13,27 +13,27 @@ const Question = ({ question }: QuestionProps): JSX.Element => {
     </div>
     <div className="col-1 d-flex align-items-center p-0">
       <div className="btn-group btn-group-toggle flex-fill grw-questionnaire-btn-group" data-toggle="buttons">
-        <label className="btn btn-outline-primary">
-          <input type="radio" name="options" id="noAnswer"/> 0
+        <label className="btn btn-outline-primary active">
+          <input type="radio" name={`question-${question._id}`} id={`${question._id}-noAnswer`}/> 0
         </label>
       </div>
     </div>
     <div className="col-5 d-flex align-items-center">
       <div className="btn-group btn-group-toggle flex-fill grw-questionnaire-btn-group" data-toggle="buttons">
         <label className="btn btn-outline-primary">
-          <input type="radio" name="options" id="option1"/> 1
+          <input type="radio" name={`question-${question._id}`} id={`${question._id}-option1`}/> 1
         </label>
         <label className="btn btn-outline-primary">
-          <input type="radio" name="options" id="option2" /> 2
+          <input type="radio" name={`question-${question._id}`} id={`${question._id}-option2`}/> 2
         </label>
         <label className="btn btn-outline-primary">
-          <input type="radio" name="options" id="option3" /> 3
+          <input type="radio" name={`question-${question._id}`} id={`${question._id}-option3`}/> 3
         </label>
         <label className="btn btn-outline-primary">
-          <input type="radio" name="options" id="option3" /> 4
+          <input type="radio" name={`question-${question._id}`} id={`${question._id}-option4`}/> 4
         </label>
         <label className="btn btn-outline-primary">
-          <input type="radio" name="options" id="option3" /> 5
+          <input type="radio" name={`question-${question._id}`} id={`${question._id}-option5`}/> 5
         </label>
       </div>
     </div>

+ 13 - 13
packages/app/src/components/Questionnaire/QuestionnaireModal.tsx

@@ -5,18 +5,18 @@ import {
   Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
 
+import { IQuestionnaireOrderHasId } from '~/interfaces/questionnaire/questionnaire-order';
 import { useQuestionnaireModal } from '~/stores/modal';
-import { useSWRxQuestionnaireOrders } from '~/stores/questionnaire';
 
 import Question from './Question';
 
-const QuestionnaireModal = (): JSX.Element => {
-  const { data: questionnaireModalData, close: closeQuestionnaireModal } = useQuestionnaireModal();
-  const isOpened = questionnaireModalData?.isOpened;
-
-  const { data: questionnaireOrders } = useSWRxQuestionnaireOrders();
+type QuestionnaireModalProps = {
+  questionnaireOrder: IQuestionnaireOrderHasId
+}
 
-  const questions = questionnaireOrders?.flatMap(questionnaireOrder => questionnaireOrder.questions);
+const QuestionnaireModal = ({ questionnaireOrder }: QuestionnaireModalProps): JSX.Element => {
+  const { data: questionnaireModalData, close: closeQuestionnaireModal } = useQuestionnaireModal();
+  const isOpened = questionnaireModalData?.[questionnaireOrder._id];
 
   const [answered, setAnswered] = useState(false);
 
@@ -25,22 +25,22 @@ const QuestionnaireModal = (): JSX.Element => {
   return (<Modal
     size="lg"
     isOpen={isOpened}
-    toggle={() => closeQuestionnaireModal()}
+    toggle={() => closeQuestionnaireModal(questionnaireOrder._id)}
   >
     <ModalHeader
       tag="h4"
-      toggle={() => closeQuestionnaireModal()}
+      toggle={() => closeQuestionnaireModal(questionnaireOrder._id)}
       className="bg-primary text-light">
       {answered
         ? <span className="mr-auto">{t('questionnaire.thank you for answering')}</span>
-        : <span>アンケートのタイトル</span>
+        : <span>{t('questionnaire.Give us feedback for improvements')}</span>
       }
     </ModalHeader>
     <ModalBody className="my-4">
       {answered
         ? <>その他ご意見ご要望は<a href="">こちら</a>からお願い致します。</>
         : <div className="container">
-          <h3 className="grw-modal-head">{t('questionnaire.Give us feedback for improvements')}</h3>
+          <h3 className="grw-modal-head">{questionnaireOrder.title}</h3>
           <div className="row mt-4">
             <div className="col-6"></div>
             <div className="col-1 p-0 font-weight-bold text-center">{t('questionnaire.no answer')}</div>
@@ -49,7 +49,7 @@ const QuestionnaireModal = (): JSX.Element => {
               <span className="font-weight-bold pr-3">{t('questionnaire.agree')}</span>
             </div>
           </div>
-          {questions?.map((question) => {
+          {questionnaireOrder.questions?.map((question) => {
             return <Question question={question} key={question._id.toString()}/>;
           })}
         </div>
@@ -57,7 +57,7 @@ const QuestionnaireModal = (): JSX.Element => {
     </ModalBody>
     <ModalFooter>
       {answered
-        ? <button type="button" className="btn btn-primary" onClick={() => closeQuestionnaireModal()}>{t('Close')}</button>
+        ? <button type="button" className="btn btn-primary" onClick={() => closeQuestionnaireModal(questionnaireOrder._id)}>{t('Close')}</button>
         : <>
           <div className="form-check form-check-inline mr-4">
             <input className="form-check-input" type="checkbox"/>

+ 15 - 0
packages/app/src/components/Questionnaire/QuestionnaireModalManager.tsx

@@ -0,0 +1,15 @@
+import { useSWRxQuestionnaireOrders } from '~/stores/questionnaire';
+
+import QuestionnaireModal from './QuestionnaireModal';
+
+const QuestionnaireModalManager = ():JSX.Element => {
+  const { data: questionnaireOrders } = useSWRxQuestionnaireOrders();
+
+  return <>
+    {questionnaireOrders?.map((questionnaireOrder) => {
+      return <QuestionnaireModal questionnaireOrder={questionnaireOrder} key={questionnaireOrder._id} />;
+    })}
+  </>;
+};
+
+export default QuestionnaireModalManager;

+ 34 - 0
packages/app/src/components/Questionnaire/QuestionnaireToast.tsx

@@ -0,0 +1,34 @@
+import { useState } from 'react';
+
+import { IQuestionnaireOrderHasId } from '~/interfaces/questionnaire/questionnaire-order';
+import { useQuestionnaireModal } from '~/stores/modal';
+
+
+type QuestionnaireToastProps = {
+  questionnaireOrder: IQuestionnaireOrderHasId,
+}
+
+const QuestionnaireToast = ({ questionnaireOrder }: QuestionnaireToastProps): JSX.Element => {
+  const { open: openQuestionnaireModal } = useQuestionnaireModal();
+  const [isOpen, setIsOpen] = useState(true);
+
+  const answerBtnClickHandler = () => {
+    setIsOpen(false);
+    openQuestionnaireModal(questionnaireOrder._id);
+  };
+
+  return <div className={`toast ${isOpen ? 'show' : 'hide'}`}>
+    <div className="toast-header bg-info">
+      <strong className="mr-auto text-light">{questionnaireOrder.title}</strong>
+      <button type="button" className="ml-2 mb-1 close" onClick={() => setIsOpen(false)}>
+        <span aria-hidden="true" className="text-light">&times;</span>
+      </button>
+    </div>
+    <div className="toast-body bg-light">
+      <button type="button" className="btn btn-secondary mr-3" onClick={answerBtnClickHandler}>回答する</button>
+      <button type="button" className="btn btn-secondary" onClick={() => setIsOpen(false)}>スキップする</button>
+    </div>
+  </div>;
+};
+
+export default QuestionnaireToast;

+ 2 - 0
packages/app/src/interfaces/questionnaire/questionnaire-order.ts

@@ -4,6 +4,7 @@ import { ICondition, IConditionHasId } from './condition';
 import { IQuestion, IQuestionHasId } from './question';
 
 export interface IQuestionnaireOrder {
+  title: string,
   showFrom: Date
   showUntil: Date
   questions: IQuestion[]
@@ -11,6 +12,7 @@ export interface IQuestionnaireOrder {
 }
 
 export type IQuestionnaireOrderHasId = {
+  title: string,
   showFrom: Date
   showUntil: Date
   questions: IQuestionHasId[]

+ 6 - 2
packages/app/src/pages/[[...path]].page.tsx

@@ -17,7 +17,9 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
 import { useRouter } from 'next/router';
+import { renderToString } from 'react-dom/server';
 import superjson from 'superjson';
+import * as toastr from 'toastr';
 
 import { useCurrentGrowiLayoutFluidClassName } from '~/client/services/layout';
 import { Comments } from '~/components/Comments';
@@ -25,7 +27,8 @@ import { MainPane } from '~/components/Layout/MainPane';
 import { PageAlerts } from '~/components/PageAlert/PageAlerts';
 // import { useTranslation } from '~/i18n';
 import { PageContentFooter } from '~/components/PageContentFooter';
-import QuestionnaireModal from '~/components/Questionnaire/QuestionnaireModal';
+import PopUps from '~/components/PopUps';
+import QuestionnaireModalManager from '~/components/Questionnaire/QuestionnaireModalManager';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 import { UsersHomePageFooterProps } from '~/components/UsersHomePageFooter';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
@@ -373,7 +376,8 @@ Page.getLayout = function getLayout(page) {
       <DescendantsPageListModal />
       <DrawioModal />
       <HandsontableModal />
-      <QuestionnaireModal />
+      <QuestionnaireModalManager />
+      <PopUps />
     </>
   );
 };

+ 1 - 0
packages/app/src/server/models/questionnaire/questionnaire-order.ts

@@ -11,6 +11,7 @@ export interface QuestionnaireOrderDocument extends IQuestionnaireOrder, Documen
 export type QuestionnaireOrderModel = Model<QuestionnaireOrderDocument>
 
 const questionnaireOrderSchema = new Schema<QuestionnaireOrderDocument>({
+  title: { type: String, required: true },
   showFrom: { type: Date, required: true },
   showUntil: {
     type: Date,

+ 12 - 10
packages/app/src/stores/modal.tsx

@@ -582,24 +582,26 @@ export const useConflictDiffModal = (): SWRResponse<ConflictDiffModalStatus, Err
 };
 
 /*
-* QuestionnaireModal
+* QuestionnaireModals
 */
-type QuestionnaireModalStatus = {
-  isOpened: boolean,
+type QuestionnaireModalStatuses = {
+  // key: QuestionnaireOrder _id
+  // value: whether or not the modal of QuestionnaireOrder is opened
+  [key: string]: boolean,
 }
 
 type QuestionnaireModalStatusUtils = {
-  open(): Promise<QuestionnaireModalStatus | undefined>
-  close(): Promise<QuestionnaireModalStatus | undefined>
+  open(string): Promise<QuestionnaireModalStatuses | undefined>
+  close(string): Promise<QuestionnaireModalStatuses | undefined>
 }
 
-export const useQuestionnaireModal = (status?: QuestionnaireModalStatus): SWRResponse<QuestionnaireModalStatus, Error> & QuestionnaireModalStatusUtils => {
-  const initialData: QuestionnaireModalStatus = { isOpened: true };
-  const swrResponse = useStaticSWR<QuestionnaireModalStatus, Error>('questionnaireModalStatus', status, { fallbackData: initialData });
+export const useQuestionnaireModal = (status?: QuestionnaireModalStatuses): SWRResponse<QuestionnaireModalStatuses, Error> & QuestionnaireModalStatusUtils => {
+  const initialData: QuestionnaireModalStatuses = {};
+  const swrResponse = useStaticSWR<QuestionnaireModalStatuses, Error>('questionnaireModalStatus', status, { fallbackData: initialData });
 
   return {
     ...swrResponse,
-    open: () => swrResponse.mutate({ isOpened: true }),
-    close: () => swrResponse.mutate({ isOpened: false }),
+    open: (questionnaireOrderId: string) => swrResponse.mutate({ [questionnaireOrderId]: true }),
+    close: (questionnaireOrderId: string) => swrResponse.mutate({ [questionnaireOrderId]: false }),
   };
 };