Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

/projects/:projectId/members #58

Merged
merged 14 commits into from
Jan 4, 2024
64 changes: 64 additions & 0 deletions src/components/Projects/ProjectMember.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<script lang="ts" setup>
import { User, YearWithSemesterDuration } from '/@/lib/apis'
import UserIcon from '/@/components/UI/UserIcon.vue'
import FormProjectDuration from '/@/components/UI/FormProjectDuration.vue'
import Icon from '/@/components/UI/Icon.vue'
import { computed } from 'vue'
interface Props {
user: User
modelValue: YearWithSemesterDuration
}

const props = defineProps<Props>()

const value = computed({
get() {
return props.modelValue
},
set(v) {
emit('update', v)
}
})
mehm8128 marked this conversation as resolved.
Show resolved Hide resolved

const emit = defineEmits<{
(e: 'delete', value: string): void
(e: 'update', value: YearWithSemesterDuration): void
}>()
</script>

<template>
<div :class="$style.container">
<div :class="$style.content">
<user-icon :user-id="user.id" :size="48" />
<p :class="$style.name">{{ user.name }}</p>
<form-project-duration v-model="value" since-required />
</div>
<div @click="emit('delete', user.id)">
<icon :size="32" name="mdi:delete" :class="$style.icon" />
</div>
mehm8128 marked this conversation as resolved.
Show resolved Hide resolved
</div>
</template>

<style lang="scss" module>
.container {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem;

border: 1px solid $color-primary-text;
border-radius: 8px;
}

.content {
display: flex;
align-items: center;
gap: 0.5rem;
mehm8128 marked this conversation as resolved.
Show resolved Hide resolved
}
.icon {
color: $color-secondary;
&:hover {
opacity: 0.8;
}
}
</style>
14 changes: 7 additions & 7 deletions src/components/UI/BaseSelect.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
<script lang="ts" setup>
<script lang="ts" setup generic="T">
import vSelect from 'vue-select'
import 'vue-select/dist/vue-select.css'
import { computed } from 'vue'
import Icon from '/@/components/UI/Icon.vue'

export interface Option {
export interface Option<T> {
label: string
value: string
value: T
}

interface Props {
modelValue: string
options: Option[]
modelValue: T
options: Option<T>[]
searchable?: boolean
}

const props = defineProps<Props>()

const emit = defineEmits<{
(e: 'update:modelValue', value: string): void
(e: 'update:modelValue', value: T): void
}>()

const value = computed({
Expand All @@ -33,7 +33,7 @@ const value = computed({
:options="options"
:clearable="false"
label="label"
:reduce="(option:Option) => option.value"
:reduce="(option: Option<T>) => option.value"
:class="$style.select"
:searchable="searchable"
>
Expand Down
99 changes: 45 additions & 54 deletions src/components/UI/FormProjectDuration.vue
Original file line number Diff line number Diff line change
@@ -1,48 +1,56 @@
<script lang="ts" setup>
import { Semester, YearWithSemesterDuration } from '/@/lib/apis'
import {
Semester,
YearWithSemester,
YearWithSemesterDuration
} from '/@/lib/apis'
import RequiredChip from '/@/components/UI/RequiredChip.vue'
import { Option } from '/@/components/UI/BaseSelect.vue'
import BaseSelect from '/@/components/UI/BaseSelect.vue'

type DateType = 'sinceYear' | 'sinceSemester' | 'untilYear' | 'untilSemester'
mehm8128 marked this conversation as resolved.
Show resolved Hide resolved

interface Props {
modelValue: YearWithSemesterDuration
yearsAgo?: number
sinceRequired?: boolean
}

const props = defineProps<Props>()
const props = withDefaults(defineProps<Props>(), {
yearsAgo: 20
})
const emit = defineEmits<{
(e: 'update:modelValue', modelValue: YearWithSemesterDuration): void
}>()

const yearOptions = Array(20)
const options: Option<YearWithSemester>[] = Array(props.yearsAgo)
.fill(null)
.map((_, i) => ({
label: (new Date().getFullYear() - i).toString(),
value: (new Date().getFullYear() - i).toString()
}))
const semesterOptions = [
{ label: '前期', value: Semester.first.toString() },
{ label: '後期', value: Semester.second.toString() }
]

const handleInput = (value: string, dateType: DateType) => {
const numValue = parseInt(value)
const duration: YearWithSemesterDuration = {
since: {
year: dateType === 'sinceYear' ? numValue : props.modelValue.since.year,
semester:
dateType === 'sinceSemester'
? numValue
: props.modelValue.since.semester
.map((_, i) => [
mehm8128 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

一応 .map して .flat するのは .flatMap でかけます 🤟
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap

{
label: (new Date().getFullYear() - i).toString() + ' 前期',
value: {
year: new Date().getFullYear() - i,
semester: Semester.first
}
},
until: props.modelValue.until && {
year: dateType === 'untilYear' ? numValue : props.modelValue.until.year,
semester:
dateType === 'untilSemester'
? numValue
: props.modelValue.until.semester
{
label: (new Date().getFullYear() - i).toString() + ' 後期',
value: {
year: new Date().getFullYear() - i,
semester: Semester.second
}
}
])
.flat()

const handleInput = (
value: YearWithSemester | undefined,
dateType: DateType
) => {
const duration: YearWithSemesterDuration = {
since:
dateType === 'sinceYear' && value !== undefined
? value
: props.modelValue.since,
until: dateType === 'untilYear' ? value : props.modelValue.until
}
emit('update:modelValue', duration)
}
Expand All @@ -56,18 +64,11 @@ const handleInput = (value: string, dateType: DateType) => {
<required-chip v-if="sinceRequired" />
</div>
<div :class="$style.form">
<div :class="$style.yearForm">
<base-select
:options="yearOptions"
:class="$style.yearInput"
:model-value="modelValue.since.year.toString()"
@update:model-value="handleInput($event, 'sinceYear')"
/>年
</div>
<base-select
:options="semesterOptions"
:model-value="modelValue.since.semester.toString()"
@update:model-value="handleInput($event, 'sinceSemester')"
:options="options"
:class="$style.yearInput"
:model-value="modelValue.since"
@update:model-value="handleInput($event, 'sinceYear')"
/>
</div>
</div>
Expand All @@ -77,21 +78,11 @@ const handleInput = (value: string, dateType: DateType) => {
<p :class="$style.head">~まで</p>
</div>
<div :class="$style.form">
<div :class="$style.yearForm">
<base-select
:options="yearOptions"
:class="$style.yearInput"
:model-value="
modelValue.until?.year.toString() ??
new Date().getFullYear().toString()
"
@update:model-value="handleInput($event, 'untilYear')"
/>年
</div>
<base-select
:options="semesterOptions"
:model-value="modelValue.until?.semester.toString() ?? '前期'"
@update:model-value="handleInput($event, 'untilSemester')"
:options="options"
:class="$style.yearInput"
:model-value="modelValue.until"
@update:model-value="handleInput($event, 'untilYear')"
/>
</div>
</div>
Expand Down
6 changes: 5 additions & 1 deletion src/components/UI/MemberInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ const users = await userStore.fetchUsers()

interface Props {
modelValue: User[]
isDisabled: boolean
}

const props = defineProps<Props>()
const props = withDefaults(defineProps<Props>(), {
isDisabled: false
})

const emit = defineEmits<{
(e: 'update:modelValue', value: User[]): void
Expand Down Expand Up @@ -82,6 +85,7 @@ const onClose = () => {
multiple
:close-on-select="false"
deselect-from-dropdown
:disabled="isDisabled"
@open="onOpen"
@close="onClose"
@search="onSearch"
Expand Down
82 changes: 82 additions & 0 deletions src/pages/ProjectMembers.vue
mehm8128 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<script lang="ts" setup>
import PageContainer from '/@/components/Layout/PageContainer.vue'
import ContentHeader from '/@/components/Layout/ContentHeader.vue'
import useParam from '/@/use/param'
import MemberInput from '/@/components/UI/MemberInput.vue'
import { ref, watchEffect, computed } from 'vue'
import BaseButton from '/@/components/UI/BaseButton.vue'
import apis, { ProjectDetail } from '/@/lib/apis'

const projectId = useParam('id')
const projectDetail = ref<ProjectDetail>()

watchEffect(async () => {
if (projectId.value) {
projectDetail.value = (await apis.getProject(projectId.value)).data
}
})
const members = computed(() => projectDetail.value?.members ?? [])
const name = computed(() => projectDetail.value?.name ?? 'Loading... ')

const fetchState = computed(() => projectDetail.value !== undefined)
</script>

<template>
<page-container>
<content-header
icon-name="mdi:clipboard-file-outline"
:header-texts="[
{ title: 'Projects', url: '/projects' },
{ title: `${name}`, url: `/projects/${projectId}` },
{ title: 'Members', url: '/projects/${projectId}/members' }
]"
detail="プロジェクトを変更します"
:class="$style.header"
/>
<h2 :class="$style.memberText">メンバー</h2>
<member-input
v-model="members"
:class="$style.memberInput"
:is-disabled="!fetchState"
/>
<div :class="$style.buttonContainer">
<router-link :to="`/projects/${projectId}`" :class="$style.link">
<base-button icon="mdi:arrow-left" type="secondary">Back</base-button>
</router-link>
<router-link :to="`/projects/${projectId}/edit`" :class="$style.link">
<base-button
icon="mdi:arrow-right"
type="primary"
:is-disabled="!fetchState"
>Next</base-button
>
</router-link>
</div>
</page-container>
</template>

<style lang="scss" module>
.header {
margin: 4rem 0 2rem;
}

.memberText {
font-size: 1.25rem;
margin-bottom: 0.5rem;
}

.memberInput {
margin-bottom: 2rem;
margin-left: 0.5rem;
}

.buttonContainer {
display: flex;
gap: 1rem;
}

.link {
text-decoration: none;
color: inherit;
}
</style>
Loading