Add basic interface for skills

Skills change when the job changes, but can't be selected on their own yet
This commit is contained in:
Justin Edmund 2022-11-28 20:36:12 -08:00
parent c599a8352a
commit 79a0095d22
5 changed files with 291 additions and 144 deletions

View file

@ -1,11 +1,10 @@
import React, { useCallback, useEffect, useState } from 'react' import React, { useEffect, useState } from "react"
import { useRouter } from 'next/router' import { useRouter } from "next/router"
import api from '~utils/api' import { appState } from "~utils/appState"
import { appState } from '~utils/appState' import { jobGroups } from "~utils/jobGroups"
import { jobGroups } from '~utils/jobGroups'
import './index.scss' import "./index.scss"
// Props // Props
interface Props { interface Props {
@ -16,10 +15,11 @@ interface Props {
type GroupedJob = { [key: string]: Job[] } type GroupedJob = { [key: string]: Job[] }
const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(function useFieldSet(props, ref) { const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
function useFieldSet(props, ref) {
// Set up router for locale // Set up router for locale
const router = useRouter() const router = useRouter()
const locale = router.locale || 'en' const locale = router.locale || "en"
// Set up local states for storing jobs // Set up local states for storing jobs
const [currentJob, setCurrentJob] = useState<Job>() const [currentJob, setCurrentJob] = useState<Job>()
@ -27,37 +27,32 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(function useField
const [sortedJobs, setSortedJobs] = useState<GroupedJob>() const [sortedJobs, setSortedJobs] = useState<GroupedJob>()
// Organize jobs into groups on mount // Organize jobs into groups on mount
const organizeJobs = useCallback((jobs: Job[]) => { useEffect(() => {
const jobGroups = jobs.map(job => job.row).filter((value, index, self) => self.indexOf(value) === index) const jobGroups = appState.jobs
.map((job) => job.row)
.filter((value, index, self) => self.indexOf(value) === index)
let groupedJobs: GroupedJob = {} let groupedJobs: GroupedJob = {}
jobGroups.forEach(group => { jobGroups.forEach((group) => {
groupedJobs[group] = jobs.filter(job => job.row === group) groupedJobs[group] = appState.jobs.filter((job) => job.row === group)
}) })
setJobs(jobs) setJobs(appState.jobs)
setSortedJobs(groupedJobs) setSortedJobs(groupedJobs)
appState.jobs = jobs }, [appState])
}, [])
// Fetch all jobs on mount
useEffect(() => {
api.endpoints.jobs.getAll()
.then(response => organizeJobs(response.data))
}, [organizeJobs])
// Set current job on mount // Set current job on mount
useEffect(() => { useEffect(() => {
if (jobs && props.currentJob) { if (jobs && props.currentJob) {
const job = jobs.find(job => job.id === props.currentJob) const job = appState.jobs.find((job) => job.id === props.currentJob)
setCurrentJob(job) setCurrentJob(job)
} }
}, [jobs, props.currentJob]) }, [appState, props.currentJob])
// Enable changing select value // Enable changing select value
function handleChange(event: React.ChangeEvent<HTMLSelectElement>) { function handleChange(event: React.ChangeEvent<HTMLSelectElement>) {
if (jobs) { if (jobs) {
const job = jobs.find(job => job.id === event.target.value) const job = jobs.find((job) => job.id === event.target.value)
if (props.onChange) props.onChange(job) if (props.onChange) props.onChange(job)
setCurrentJob(job) setCurrentJob(job)
} }
@ -65,14 +60,20 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(function useField
// Render JSX for each job option, sorted into optgroups // Render JSX for each job option, sorted into optgroups
function renderJobGroup(group: string) { function renderJobGroup(group: string) {
const options = sortedJobs && sortedJobs[group].length > 0 && const options =
sortedJobs[group].sort((a, b) => a.order - b.order).map((item, i) => { sortedJobs &&
sortedJobs[group].length > 0 &&
sortedJobs[group]
.sort((a, b) => a.order - b.order)
.map((item, i) => {
return ( return (
<option key={i} value={item.id}>{item.name[locale]}</option> <option key={i} value={item.id}>
{item.name[locale]}
</option>
) )
}) })
const groupName = jobGroups.find(g => g.slug === group)?.name[locale] const groupName = jobGroups.find((g) => g.slug === group)?.name[locale]
return ( return (
<optgroup key={group} label={groupName}> <optgroup key={group} label={groupName}>
@ -87,11 +88,17 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(function useField
value={currentJob?.id} value={currentJob?.id}
onBlur={props.onBlur} onBlur={props.onBlur}
onChange={handleChange} onChange={handleChange}
ref={ref}> ref={ref}
<option key="no-job" value={-1}>No class</option> >
{ (sortedJobs) ? Object.keys(sortedJobs).map(x => renderJobGroup(x)) : '' } <option key="no-job" value={-1}>
No class
</option>
{sortedJobs
? Object.keys(sortedJobs).map((x) => renderJobGroup(x))
: ""}
</select> </select>
) )
}) }
)
export default JobDropdown export default JobDropdown

View file

@ -7,16 +7,31 @@
width: auto; width: auto;
} }
.JobDetails {
display: flex;
flex-direction: column;
width: 100%;
select {
flex-grow: 0;
}
.JobSkills {
flex-grow: 2;
}
}
.JobImage { .JobImage {
$height: 249px; $height: 249px;
$width: 447px; $width: 447px;
background: url('/images/background_a.jpg'); background: url("/images/background_a.jpg");
background-size: 500px 281px; background-size: 500px 281px;
border-radius: $unit; border-radius: $unit;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
display: block; display: block;
flex-grow: 2; flex-grow: 2;
flex-shrink: 0;
height: $height; height: $height;
margin-right: $unit * 3; margin-right: $unit * 3;
max-height: $height; max-height: $height;
@ -41,4 +56,10 @@
z-index: 1; z-index: 1;
} }
} }
.JobSkills {
display: flex;
flex-direction: column;
gap: $unit;
}
} }

View file

@ -1,21 +1,25 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from "react"
import { useSnapshot } from 'valtio' 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 // Props
interface Props {} interface Props {}
const JobSection = (props: Props) => { const JobSection = (props: Props) => {
const [job, setJob] = useState<Job>() const [job, setJob] = useState<Job>()
const [imageUrl, setImageUrl] = useState('') const [imageUrl, setImageUrl] = useState("")
const { party } = useSnapshot(appState) const { party } = useSnapshot(appState)
const [numSkills, setNumSkills] = useState(4)
const [skills, setSkills] = useState<JobSkill[]>([])
useEffect(() => { useEffect(() => {
// Set current job based on ID // Set current job based on ID
setJob(party.job) setJob(party.job)
@ -30,15 +34,27 @@ const JobSection = (props: Props) => {
}, [job]) }, [job])
function receiveJob(job?: Job) { function receiveJob(job?: Job) {
console.log(`Receiving job! Row ${job?.row}: ${job?.name.en}`)
if (job) {
setJob(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() { function generateImageUrl() {
let imgSrc = "" let imgSrc = ""
if (job) { if (job) {
const slug = job?.name.en.replaceAll(' ', '-').toLowerCase() const slug = job?.name.en.replaceAll(" ", "-").toLowerCase()
const gender = (party.user && party.user.gender == 1) ? 'b' : 'a' const gender = party.user && party.user.gender == 1 ? "b" : "a"
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/jobs/${slug}_${gender}.png` imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/jobs/${slug}_${gender}.png`
} }
@ -53,10 +69,19 @@ const JobSection = (props: Props) => {
<img src={imageUrl} /> <img src={imageUrl} />
<div className="Overlay" /> <div className="Overlay" />
</div> </div>
<div className="JobDetails">
<JobDropdown <JobDropdown
currentJob={ (party.job) ? party.job.id : undefined} currentJob={party.job ? party.job.id : undefined}
onChange={receiveJob} onChange={receiveJob}
/> />
<ul className="JobSkills">
{[...Array(numSkills)].map((e, i) => (
<li>
<JobSkillItem skill={skills[i]} editable={!skills[i]?.main} />
</li>
))}
</ul>
</div>
</section> </section>
) )
} }

View file

@ -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;
}
}

View file

@ -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 (
<div className={classes}>
{props.skill ? (
<img
alt={props.skill.name[locale]}
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}job-skills/${props.skill.slug}.png`}
/>
) : (
<div className="placeholder">
<PlusIcon />
</div>
)}
<div className="info">
{/* {props.skill ? <div className="skill pill">Grouping</div> : ""} */}
{props.skill ? (
<p>{props.skill.name[locale]}</p>
) : (
<p className="placeholder">Select a skill</p>
)}
</div>
</div>
)
}
export default JobSkillItem