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,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<HTMLSelectElement>) => void
currentJob?: string
onChange?: (job?: Job) => void
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void
}
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
const router = useRouter()
const locale = router.locale || 'en'
const locale = router.locale || "en"
// Set up local states for storing jobs
const [currentJob, setCurrentJob] = useState<Job>()
@ -27,71 +27,78 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(function useField
const [sortedJobs, setSortedJobs] = useState<GroupedJob>()
// 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<HTMLSelectElement>) {
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 (
<option key={i} value={item.id}>{item.name[locale]}</option>
)
})
const options =
sortedJobs &&
sortedJobs[group].length > 0 &&
sortedJobs[group]
.sort((a, b) => a.order - b.order)
.map((item, i) => {
return (
<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 (
<optgroup key={group} label={groupName}>
{options}
</optgroup>
)
return (
<optgroup key={group} label={groupName}>
{options}
</optgroup>
)
}
return (
<select
key={currentJob?.id}
value={currentJob?.id}
onBlur={props.onBlur}
onChange={handleChange}
ref={ref}>
<option key="no-job" value={-1}>No class</option>
{ (sortedJobs) ? Object.keys(sortedJobs).map(x => renderJobGroup(x)) : '' }
</select>
<select
key={currentJob?.id}
value={currentJob?.id}
onBlur={props.onBlur}
onChange={handleChange}
ref={ref}
>
<option key="no-job" value={-1}>
No class
</option>
{sortedJobs
? Object.keys(sortedJobs).map((x) => renderJobGroup(x))
: ""}
</select>
)
})
}
)
export default JobDropdown

View file

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

View file

@ -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<Job>()
const [imageUrl, setImageUrl] = useState('')
const [job, setJob] = useState<Job>()
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<JobSkill[]>([])
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 (
<section id="Job">
<div className="JobImage">
<img src={imageUrl} />
<div className="Overlay" />
</div>
<JobDropdown
currentJob={ (party.job) ? party.job.id : undefined}
onChange={receiveJob}
/>
</section>
)
// Render: JSX components
return (
<section id="Job">
<div className="JobImage">
<img src={imageUrl} />
<div className="Overlay" />
</div>
<div className="JobDetails">
<JobDropdown
currentJob={party.job ? party.job.id : undefined}
onChange={receiveJob}
/>
<ul className="JobSkills">
{[...Array(numSkills)].map((e, i) => (
<li>
<JobSkillItem skill={skills[i]} editable={!skills[i]?.main} />
</li>
))}
</ul>
</div>
</section>
)
}
export default JobSection
export default JobSection

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