diff --git a/apps/survey-web/src/app/app.css.ts b/apps/survey-web/src/app/app.css.ts new file mode 100644 index 0000000..4bb23aa --- /dev/null +++ b/apps/survey-web/src/app/app.css.ts @@ -0,0 +1,9 @@ +import { style } from '@vanilla-extract/css'; + +export const container = style({ + margin: 'auto', + maxWidth: '90vw', + width: '640px', + marginTop: '12px', + marginBottom: '12px', +}); diff --git a/apps/survey-web/src/app/app.tsx b/apps/survey-web/src/app/app.tsx index dee085e..d4cd639 100644 --- a/apps/survey-web/src/app/app.tsx +++ b/apps/survey-web/src/app/app.tsx @@ -1,29 +1,22 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars -import { SharedUi } from '@ssoon-servey/shared-ui'; import { Route, Routes, Link } from 'react-router-dom'; +import { container } from './app.css'; +import SurveyPage from './survey/page'; export function App() { return ( -
- +
This is the generated root route.{' '} - Click here for page 2. -
- } - /> - - Click here to go back to root page. + Click here for page survey.
} /> + } /> {/* END: routes */} diff --git a/apps/survey-web/src/app/survey/hooks/useSurvey.ts b/apps/survey-web/src/app/survey/hooks/useSurvey.ts new file mode 100644 index 0000000..8c95d88 --- /dev/null +++ b/apps/survey-web/src/app/survey/hooks/useSurvey.ts @@ -0,0 +1,111 @@ +import { + SupabaseContextValue, + useSupabaseContext, +} from '@ssoon-servey/supabase'; +import { useEffect, useState } from 'react'; + +type Options = { + id: number; + option_text: string; + item_id: number | null; +}; + +type SurveyItems = { + id: number; + options: Options[]; + question_required: boolean; + question_title: string; + question_type: string; + section_id: number | null; +}; + +type SurveySections = { + id: number; + survey_title: string | null; + survey_id: number | null; + items: SurveyItems[]; + isNext: boolean; + isPrevious: boolean; +}; + +type Survey = { + id: number; + title: string; + description?: string | null; + + sections: SurveySections[]; +}; + +type apiState = + | { + data: null; + isLoading: false; + isError: true; + } + | { + data: null; + isLoading: true; + isError: false; + } + | { + data: T; + isLoading: false; + isError: false; + }; + +const getSurvey = async (supabase: SupabaseContextValue['supabase']) => { + const { data: surveys } = await supabase + .from('surveys') + .select('id,title,description'); + if (!surveys) return null; + + const _survey = surveys[0]; + + const { data: sections } = await supabase.from('survey_sections').select('*'); + if (!sections) return null; + + const { data: items } = await supabase.from('survey_items').select('*'); + if (!items) return null; + + const { data: options } = await supabase.from('question_options').select('*'); + if (!options) return null; + + const survey: Survey = { + ..._survey, + sections: sections.map((section, i) => ({ + ...section, + isNext: i !== sections.length - 1, + isPrevious: i !== 0, + items: items + .filter((item) => item.section_id === section.id) + .map((items) => ({ + ...items, + options: options.filter((option) => option.item_id === items.id), + })), + })), + }; + return survey; +}; + +const useGetSurvey = (): apiState => { + const { supabase } = useSupabaseContext(); + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isError, setIsError] = useState(false); + + useEffect(() => { + getSurvey(supabase) + .then((data) => { + setData(data); + setIsLoading(false); + }) + .catch(() => { + setIsError(true); + setIsLoading(false); + }); + }, [supabase]); + + return { data, isLoading, isError } as apiState; +}; + +export { useGetSurvey }; diff --git a/apps/survey-web/src/app/survey/page.css.ts b/apps/survey-web/src/app/survey/page.css.ts new file mode 100644 index 0000000..a3f0f7b --- /dev/null +++ b/apps/survey-web/src/app/survey/page.css.ts @@ -0,0 +1,24 @@ +import { vars } from '@ssoon-servey/shared-ui'; +import { style } from '@vanilla-extract/css'; + +export const cardContainer = style({ + display: 'flex', + flexDirection: 'column', + gap: '10px', +}); + +export const borderTop = style({ + backgroundColor: vars.color.primary500, + borderTopLeftRadius: '8px', + borderTopRightRadius: '8px', + height: '10px', + position: 'absolute', + top: 0, + left: 0, + right: 0, +}); + +export const cardWrapper = style({ + padding: '24px', + paddingTop: '22px', +}); diff --git a/apps/survey-web/src/app/survey/page.tsx b/apps/survey-web/src/app/survey/page.tsx new file mode 100644 index 0000000..548486b --- /dev/null +++ b/apps/survey-web/src/app/survey/page.tsx @@ -0,0 +1,66 @@ +import { useGetSurvey } from './hooks/useSurvey'; +import { Block, Card } from '@ssoon-servey/shared-ui'; +import * as $ from './page.css'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useEffect } from 'react'; + +const INITIAL_ID = 1; + +const SurveyPage = () => { + const { data, isError, isLoading } = useGetSurvey(); + const { id } = useParams(); + const sectionId = Number(id); + + const navigate = useNavigate(); + + useEffect(() => { + navigate(`/survey/${INITIAL_ID}`, { replace: true }); + }, []); + + if (isError) { + return
error
; + } + + if (isLoading) { + return
loading...
; + } + + const goNextSection = () => { + navigate(`/survey/${sectionId + 1}`); + }; + + const goBackSection = () => { + navigate(-1); + }; + + const sections = data.sections[sectionId - 1]; + + return ( +
+ +
+
+ {data?.title} +
* 표시는 필수 질문임
+
+ + {sections.items.map((item) => ( + +
+
{item.question_title}
+ {item.options.map((option) => ( +
{option.option_text}
+ ))} +
+
+ ))} + +
+ {sections.isPrevious && } + {sections.isNext && } +
+
+ ); +}; + +export default SurveyPage; diff --git a/apps/survey-web/src/main.tsx b/apps/survey-web/src/main.tsx index 563fabf..cd65457 100644 --- a/apps/survey-web/src/main.tsx +++ b/apps/survey-web/src/main.tsx @@ -1,8 +1,9 @@ import { StrictMode } from 'react'; import * as ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; - +import { SupabaseProvider } from '@ssoon-servey/supabase'; import App from './app/app'; +import '@ssoon-servey/shared-ui/css'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement @@ -10,7 +11,9 @@ const root = ReactDOM.createRoot( root.render( - + + + ); diff --git a/apps/survey-web/vite.config.ts b/apps/survey-web/vite.config.ts index 3d8a345..9ea868f 100644 --- a/apps/survey-web/vite.config.ts +++ b/apps/survey-web/vite.config.ts @@ -2,6 +2,7 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin'; export default defineConfig({ root: __dirname, @@ -17,7 +18,7 @@ export default defineConfig({ host: 'localhost', }, - plugins: [react(), nxViteTsPaths()], + plugins: [react(), nxViteTsPaths(), vanillaExtractPlugin()], // Uncomment this if you are using workers. // worker: { diff --git a/libs/supabase/src/types/index.ts b/libs/supabase/src/types/index.ts index 2364c1b..e754f51 100644 --- a/libs/supabase/src/types/index.ts +++ b/libs/supabase/src/types/index.ts @@ -37,7 +37,6 @@ export type Database = { }; survey_items: { Row: { - hasOption: boolean | null; id: number; question_required: boolean; question_title: string; @@ -45,7 +44,6 @@ export type Database = { section_id: number | null; }; Insert: { - hasOption?: boolean | null; id?: never; question_required: boolean; question_title: string; @@ -53,7 +51,6 @@ export type Database = { section_id?: number | null; }; Update: { - hasOption?: boolean | null; id?: never; question_required?: boolean; question_title?: string; @@ -73,19 +70,16 @@ export type Database = { survey_sections: { Row: { id: number; - section_id: number | null; survey_id: number | null; survey_title: string | null; }; Insert: { id?: never; - section_id?: number | null; survey_id?: number | null; survey_title?: string | null; }; Update: { id?: never; - section_id?: number | null; survey_id?: number | null; survey_title?: string | null; };