From 79a0095d225b537360b1708b1625b2fdf04d35d6 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 28 Nov 2022 20:36:12 -0800 Subject: [PATCH] Add basic interface for skills Skills change when the job changes, but can't be selected on their own yet --- components/JobDropdown/index.tsx | 129 +++++++++++++++-------------- components/JobSection/index.scss | 95 ++++++++++++--------- components/JobSection/index.tsx | 117 ++++++++++++++++---------- components/JobSkillItem/index.scss | 42 ++++++++++ components/JobSkillItem/index.tsx | 52 ++++++++++++ 5 files changed, 291 insertions(+), 144 deletions(-) create mode 100644 components/JobSkillItem/index.scss create mode 100644 components/JobSkillItem/index.tsx diff --git a/components/JobDropdown/index.tsx b/components/JobDropdown/index.tsx index b0a6019f..187b9de5 100644 --- a/components/JobDropdown/index.tsx +++ b/components/JobDropdown/index.tsx @@ -1,25 +1,25 @@ -import React, { useCallback, useEffect, useState } from 'react' -import { useRouter } from 'next/router' +import React, { useEffect, useState } from "react" +import { useRouter } from "next/router" -import api from '~utils/api' -import { appState } from '~utils/appState' -import { jobGroups } from '~utils/jobGroups' +import { appState } from "~utils/appState" +import { jobGroups } from "~utils/jobGroups" -import './index.scss' +import "./index.scss" // Props interface Props { - currentJob?: string - onChange?: (job?: Job) => void - onBlur?: (event: React.ChangeEvent) => void + currentJob?: string + onChange?: (job?: Job) => void + onBlur?: (event: React.ChangeEvent) => void } type GroupedJob = { [key: string]: Job[] } -const JobDropdown = React.forwardRef(function useFieldSet(props, ref) { +const JobDropdown = React.forwardRef( + function useFieldSet(props, ref) { // Set up router for locale const router = useRouter() - const locale = router.locale || 'en' + const locale = router.locale || "en" // Set up local states for storing jobs const [currentJob, setCurrentJob] = useState() @@ -27,71 +27,78 @@ const JobDropdown = React.forwardRef(function useField const [sortedJobs, setSortedJobs] = useState() // Organize jobs into groups on mount - const organizeJobs = useCallback((jobs: Job[]) => { - const jobGroups = jobs.map(job => job.row).filter((value, index, self) => self.indexOf(value) === index) - let groupedJobs: GroupedJob = {} - - jobGroups.forEach(group => { - groupedJobs[group] = jobs.filter(job => job.row === group) - }) - - setJobs(jobs) - setSortedJobs(groupedJobs) - appState.jobs = jobs - }, []) - - // Fetch all jobs on mount useEffect(() => { - api.endpoints.jobs.getAll() - .then(response => organizeJobs(response.data)) - }, [organizeJobs]) + const jobGroups = appState.jobs + .map((job) => job.row) + .filter((value, index, self) => self.indexOf(value) === index) + let groupedJobs: GroupedJob = {} + + jobGroups.forEach((group) => { + groupedJobs[group] = appState.jobs.filter((job) => job.row === group) + }) + + setJobs(appState.jobs) + setSortedJobs(groupedJobs) + }, [appState]) // Set current job on mount useEffect(() => { - if (jobs && props.currentJob) { - const job = jobs.find(job => job.id === props.currentJob) - setCurrentJob(job) - } - }, [jobs, props.currentJob]) + if (jobs && props.currentJob) { + const job = appState.jobs.find((job) => job.id === props.currentJob) + setCurrentJob(job) + } + }, [appState, props.currentJob]) // Enable changing select value function handleChange(event: React.ChangeEvent) { - if (jobs) { - const job = jobs.find(job => job.id === event.target.value) - if (props.onChange) props.onChange(job) - setCurrentJob(job) - } + if (jobs) { + const job = jobs.find((job) => job.id === event.target.value) + if (props.onChange) props.onChange(job) + setCurrentJob(job) + } } // Render JSX for each job option, sorted into optgroups function renderJobGroup(group: string) { - const options = sortedJobs && sortedJobs[group].length > 0 && - sortedJobs[group].sort((a, b) => a.order - b.order).map((item, i) => { - return ( - - ) - }) + const options = + sortedJobs && + sortedJobs[group].length > 0 && + sortedJobs[group] + .sort((a, b) => a.order - b.order) + .map((item, i) => { + return ( + + ) + }) - const groupName = jobGroups.find(g => g.slug === group)?.name[locale] + const groupName = jobGroups.find((g) => g.slug === group)?.name[locale] - return ( - - {options} - - ) + return ( + + {options} + + ) } - + return ( - + ) -}) + } +) export default JobDropdown diff --git a/components/JobSection/index.scss b/components/JobSection/index.scss index bf1e710c..9348ecb2 100644 --- a/components/JobSection/index.scss +++ b/components/JobSection/index.scss @@ -1,44 +1,65 @@ #Job { + display: flex; + margin-bottom: $unit * 3; + + select { + flex-grow: 1; + width: auto; + } + + .JobDetails { display: flex; - margin-bottom: $unit * 3; + flex-direction: column; + width: 100%; select { - flex-grow: 1; - width: auto; + flex-grow: 0; } - .JobImage { - $height: 249px; - $width: 447px; - - background: url('/images/background_a.jpg'); - background-size: 500px 281px; - border-radius: $unit; - box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2); - display: block; - flex-grow: 2; - height: $height; - margin-right: $unit * 3; - max-height: $height; - max-width: $width; - overflow: hidden; - position: relative; - width: $width; - transition: box-shadow 0.15s ease-in-out; - - img { - position: relative; - top: $unit * -4; - left: 50%; - transform: translateX(-50%); - width: 100%; - z-index: 2; - } - - .Overlay { - background: rgba(255, 255, 255, 0.12); - position: absolute; - z-index: 1; - } + .JobSkills { + flex-grow: 2; } -} \ No newline at end of file + } + + .JobImage { + $height: 249px; + $width: 447px; + + background: url("/images/background_a.jpg"); + background-size: 500px 281px; + border-radius: $unit; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2); + display: block; + flex-grow: 2; + flex-shrink: 0; + height: $height; + margin-right: $unit * 3; + max-height: $height; + max-width: $width; + overflow: hidden; + position: relative; + width: $width; + transition: box-shadow 0.15s ease-in-out; + + img { + position: relative; + top: $unit * -4; + left: 50%; + transform: translateX(-50%); + width: 100%; + z-index: 2; + } + + .Overlay { + background: rgba(255, 255, 255, 0.12); + position: absolute; + z-index: 1; + } + } + + .JobSkills { + display: flex; + flex-direction: column; + gap: $unit; + } +} diff --git a/components/JobSection/index.tsx b/components/JobSection/index.tsx index 464f261f..1bd64ce1 100644 --- a/components/JobSection/index.tsx +++ b/components/JobSection/index.tsx @@ -1,64 +1,89 @@ -import React, { useEffect, useState } from 'react' -import { useSnapshot } from 'valtio' +import React, { useEffect, useState } from "react" +import { useSnapshot } from "valtio" -import JobDropdown from '~components/JobDropdown' +import JobDropdown from "~components/JobDropdown" +import JobSkillItem from "~components/JobSkillItem" -import { appState } from '~utils/appState' +import { appState } from "~utils/appState" -import './index.scss' +import "./index.scss" // Props interface Props {} const JobSection = (props: Props) => { - const [job, setJob] = useState() - const [imageUrl, setImageUrl] = useState('') + const [job, setJob] = useState() + const [imageUrl, setImageUrl] = useState("") - const { party } = useSnapshot(appState) + const { party } = useSnapshot(appState) - useEffect(() => { - // Set current job based on ID - setJob(party.job) - }, []) + const [numSkills, setNumSkills] = useState(4) + const [skills, setSkills] = useState([]) - useEffect(() => { - generateImageUrl() - }) + useEffect(() => { + // Set current job based on ID + setJob(party.job) + }, []) - useEffect(() => { - if (job) appState.party.job = job - }, [job]) + useEffect(() => { + generateImageUrl() + }) - function receiveJob(job?: Job) { - setJob(job) + useEffect(() => { + if (job) appState.party.job = job + }, [job]) + + function receiveJob(job?: Job) { + console.log(`Receiving job! Row ${job?.row}: ${job?.name.en}`) + if (job) { + setJob(job) + + const baseSkills = appState.jobSkills.filter( + (skill) => skill.job.id === job.id && skill.main + ) + + if (job.row === "1") setNumSkills(3) + else setNumSkills(4) + + setSkills(baseSkills) + } + } + + function generateImageUrl() { + let imgSrc = "" + + if (job) { + const slug = job?.name.en.replaceAll(" ", "-").toLowerCase() + const gender = party.user && party.user.gender == 1 ? "b" : "a" + + imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/jobs/${slug}_${gender}.png` } - function generateImageUrl() { - let imgSrc = "" - - if (job) { - const slug = job?.name.en.replaceAll(' ', '-').toLowerCase() - const gender = (party.user && party.user.gender == 1) ? 'b' : 'a' + setImageUrl(imgSrc) + } - imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/jobs/${slug}_${gender}.png` - } - - setImageUrl(imgSrc) - } - - // Render: JSX components - return ( -
-
- -
-
- -
- ) + // Render: JSX components + return ( +
+
+ +
+
+
+ +
    + {[...Array(numSkills)].map((e, i) => ( +
  • + +
  • + ))} +
+
+
+ ) } -export default JobSection \ No newline at end of file +export default JobSection diff --git a/components/JobSkillItem/index.scss b/components/JobSkillItem/index.scss new file mode 100644 index 00000000..3d944180 --- /dev/null +++ b/components/JobSkillItem/index.scss @@ -0,0 +1,42 @@ +.JobSkill { + display: flex; + gap: $unit; + align-items: center; + + &:hover p.placeholder { + color: $grey-20; + } + + &.editable:hover > img, + &.editable:hover > .placeholder { + border: $hover-stroke; + box-shadow: $hover-shadow; + cursor: pointer; + transform: $scale-tall; + } + + & > img, + & > .placeholder { + background: white; + border-radius: calc($unit / 2); + border: 1px solid rgba(0, 0, 0, 0); + width: $unit * 5; + height: $unit * 5; + } + + & > .placeholder { + display: flex; + align-items: center; + justify-content: center; + + & > svg { + fill: $grey-60; + width: $unit * 2; + height: $unit * 2; + } + } + + p.placeholder { + color: $grey-50; + } +} diff --git a/components/JobSkillItem/index.tsx b/components/JobSkillItem/index.tsx new file mode 100644 index 00000000..b1238863 --- /dev/null +++ b/components/JobSkillItem/index.tsx @@ -0,0 +1,52 @@ +import React from "react" +import { useRouter } from "next/router" +import { useTranslation } from "next-i18next" + +import classNames from "classnames" + +import PlusIcon from "~public/icons/Add.svg" + +import "./index.scss" + +// Props +interface Props { + skill?: JobSkill + editable: boolean +} + +const JobSkillItem = (props: Props) => { + const router = useRouter() + const { t } = useTranslation("common") + const locale = + router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en" + + const classes = classNames({ + JobSkill: true, + editable: props.editable, + }) + + return ( +
+ {props.skill ? ( + {props.skill.name[locale]} + ) : ( +
+ +
+ )} +
+ {/* {props.skill ?
Grouping
: ""} */} + {props.skill ? ( +

{props.skill.name[locale]}

+ ) : ( +

Select a skill

+ )} +
+
+ ) +} + +export default JobSkillItem