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

Contestの詳細ページの作成 #51

Merged
merged 13 commits into from
Mar 28, 2023
48 changes: 48 additions & 0 deletions src/components/Contest/ContestTeamItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<script lang="ts" setup>
import UserIcons from '/@/components/UI/UserIcons.vue'
import type { ContestTeam } from '/@/lib/apis'

interface Props {
contestId: string
contestTeam: ContestTeam
}

defineProps<Props>()
/* todo:サーバーからmembersが返ってくるようになったらcontestTeam.members.map(member=>member.id)を使う */
const userIds = ['sapphi_red', 'toshi00', 'tesso', 'mehm8128']
</script>

<template>
<router-link
:to="`/contests/${contestId}/teams/${contestTeam.id}/edit`"
:class="$style.link"
>
<div :class="$style.container">
<div>
<p :class="$style.name">{{ contestTeam.name }}</p>
<p :class="$style.result">{{ contestTeam.result }}</p>
</div>
<user-icons :user-ids="userIds" />
</div>
</router-link>
</template>

<style lang="scss" module>
.link {
color: inherit;
text-decoration: none;
}
.container {
padding: 0.5rem;
display: flex;
align-items: center;
justify-content: space-between;
}
.name {
color: $color-primary;
font-size: 1.125rem;
}
.result {
margin-top: 2rem;
}
</style>
94 changes: 94 additions & 0 deletions src/components/Contest/ContestTeams.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<script lang="ts" setup>
import BaseButton from '/@/components/UI/BaseButton.vue'

import FormInput from '/@/components/UI/FormInput.vue'
import ContestTeamItem from '/@/components/Contest/ContestTeamItem.vue'
import { RouterLink } from 'vue-router'
import { ContestTeam } from '/@/lib/apis'
import { ref } from 'vue'

interface Props {
contestId: string
contestTeams: ContestTeam[]
}

defineProps<Props>()
const emit = defineEmits<{
(e: 'input', value: string): void
}>()

const searchQuery = ref('')
</script>

<template>
<div>
<div :class="$style.searchFormContainer">
<div :class="$style.searchForm">
<p :class="$style.searchFormDescriptionText">検索</p>
<form-input
:model-value="searchQuery"
placeholder="チーム名"
icon="magnify"
@update:model-value="emit('input', $event)"
/>
</div>
<div :class="$style.newTeamLink">
<p :class="$style.searchFormDescriptionText">チーム作成</p>
<router-link
:to="`/contests/${contestId}/teams/new`"
:class="$style.link"
>
<base-button type="primary" icon="mdi:plus">New</base-button>
</router-link>
</div>
</div>
<ul :class="$style.teamList">
<li v-for="contestTeam in contestTeams" :key="contestTeam.id">
<contest-team-item
:contest-id="contestId"
:contest-team="contestTeam"
/>
</li>
</ul>
</div>
</template>

<style lang="scss" module>
.searchFormContainer {
display: flex;
align-items: center;
margin-top: 0.5rem;
}
.header {
margin: 4rem 0 2rem;
}
Copy link
Member

Choose a reason for hiding this comment

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

使ってなさそう

.searchFormDescriptionText {
color: $color-secondary;
font-size: 0.875rem;
}
.searchForm {
flex-grow: 1;
}
.newTeamLink {
margin-left: 0.5rem;
}
.link {
text-decoration: none;
color: inherit;
}
.teamList {
list-style: none;
padding: 0.5rem 0;
li {
border: 1px solid $color-primary-text;
border-radius: 8px;
margin-bottom: 0.5rem;
&:last-child {
margin-bottom: 0;
}
&:hover {
background-color: $color-background-dim;
}
}
}
</style>
2 changes: 1 addition & 1 deletion src/components/Contests/ContestItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ defineProps<Props>()
</script>

<template>
<router-link :to="`/contests/${contest.id}/edit`" :class="$style.link">
<router-link :to="`/contests/${contest.id}`" :class="$style.link">
<div :class="$style.container">
<p :class="$style.name">{{ contest.name }}</p>
<p :class="$style.duration">
Expand Down
32 changes: 32 additions & 0 deletions src/components/UI/UserIcons.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script lang="ts" setup>
import UserIcon from '/@/components/UI/UserIcon.vue'
interface Props {
userIds: string[]
}

defineProps<Props>()
</script>

<template>
<div :class="$style.userIcons">
<user-icon
v-for="(userId, i) in userIds.slice(0, 3)"
:key="userId"
:user-id="userId"
:class="$style.userIcon"
:style="{ left: `${i * 16}px` }"
/>
<span v-if="userIds.length > 3">+{{ userIds.length - 3 }}</span>
</div>
</template>

<style lang="scss" module>
.userIcons {
width: 80px;
position: relative;
text-align: right;
}
.userIcon {
position: absolute;
}
</style>
143 changes: 128 additions & 15 deletions src/pages/Contest.vue
Original file line number Diff line number Diff line change
@@ -1,20 +1,133 @@
<script lang="ts" setup>
import ContentHeader from '/@/components/Layout/ContentHeader.vue'
import PageContainer from '/@/components/Layout/PageContainer.vue'
import BaseButton from '/@/components/UI/BaseButton.vue'

import apis, { ContestDetail, ContestTeam } from '/@/lib/apis'
import { RouterLink } from 'vue-router'
import { getDisplayDuration } from '/@/lib/date'
import Icon from '/@/components/UI/Icon.vue'
import useParam from '/@/use/param'
import { useFetcher } from '/@/use/fetcher'
import ContestTeamsComponent from '/@/components/Contest/ContestTeams.vue'

const contestId = useParam('id')
const { data: contest } = useFetcher<ContestDetail>(() =>
apis.getContest(contestId.value)
)
const { data: contestTeams, fetcherState } = useFetcher<ContestTeam[]>(() =>
apis.getContestTeams(contestId.value)
)

const searchContestTeams = (serachQuery: string) => {
// todo: serverでやるかも
contestTeams.value =
contestTeams.value?.filter(contestTeam => {
const regexp = new RegExp(serachQuery, 'i')
return regexp.test(contestTeam.name)
}) ?? []
}
</script>

<template>
<page-container>
<div>ContestDetail</div>
<div :class="$style.headerContainer">
<content-header
icon-name="mdi:trophy-outline"
:header-texts="[
{ title: 'Contests', url: '/contests' },
{ title: contest?.name ?? '', url: `/contests/${contestId}` }
]"
detail="コンテストの詳細です。"
:class="$style.header"
/>
<router-link :to="`/contests/${contestId}/edit`" :class="$style.link">
<base-button type="primary" icon="mdi:pencil">Edit</base-button>
</router-link>
</div>
<div v-if="contest !== undefined && fetcherState === 'loaded'">
<section :class="$style.section">
<h2 :class="$style.h2">コンテスト名</h2>
<p :class="$style.content">{{ contest.name }}</p>
</section>
<section :class="$style.section">
<h2 :class="$style.h2">日時</h2>
<p :class="$style.content">
{{ getDisplayDuration(contest.duration) }}
</p>
</section>
<section :class="$style.section">
<h2 :class="$style.h2">リンク</h2>
<p :class="[$style.content, $style.contestLinkContainer]">
<icon name="mdi:open-in-new" />
<a :class="$style.contestLink" :href="contest.link">
{{ contest.link }}
</a>
</p>
</section>
<section :class="$style.section">
<h2 :class="$style.h2">説明</h2>
<p :class="$style.content">{{ contest.description }}</p>
</section>
<section :class="$style.section">
<h2 :class="$style.h2">チーム</h2>
<contest-teams-component
v-if="contestTeams !== undefined"
:class="$style.content"
:contest-id="contestId"
:contest-teams="contestTeams"
@input="searchContestTeams($event)"
Copy link
Member

Choose a reason for hiding this comment

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

絞り込んだあとのチームを表示する責任があるのは、ContestTeams.vueだから、ContestTeams.vueに、絞り込む機能を実装したほうがもっとスッキリかけそう。

Contest.vueでは、全チームをpropsを通して ContestTeams.vueに流し込むだけで、 ContestTeams.vueでは、searchQueryを利用していい感じに絞り込むとかかな 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

サーバーで検索機能実装してもらえたら@input時にContest.vuesearchQueryを使ってContestTeamsをfetchしたいので、それを踏まえてContest.vueに書きました
検索機能をどっちで実装するかportfolioのチャンネルで相談してみます

Copy link
Member

Choose a reason for hiding this comment

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

そしたら、ContestTeamが検索するたびに表示されるチームが減るので、いい感じに動くようにしてほしい!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ほんとですね...
サーバーで検索機能ができるまでは上で言われたように、ContestTeams.vue側で検索機能を実装することにしました

/>
</section>
</div>
<p v-else-if="fetcherState === 'loading'">ローディング中...</p>
<p v-else-if="fetcherState === 'error'">エラーが発生しました</p>

<router-link to="/contests" :class="$style.link">
<base-button
:class="$style.backButton"
type="secondary"
icon="mdi:arrow-left"
>
Back
</base-button>
</router-link>
</page-container>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import PageContainer from '/@/components/Layout/PageContainer.vue'

export default defineComponent({
name: 'Contest',
components: {
PageContainer
},
setup() {
return {}
}
})
</script>
<style lang="scss" module>
.headerContainer {
display: flex;
justify-content: space-between;
align-items: center;
}
.header {
margin: 4rem 0 2rem;
}
.link {
text-decoration: none;
color: inherit;
}
.section {
margin-bottom: 2rem;
}
.h2 {
font-weight: bold;
font-size: 20px;
Copy link
Member

Choose a reason for hiding this comment

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

font-size : 1.25rem; がいいなー

}
.content {
margin-top: 0.5rem;
padding-left: 0.5rem;
}
.contestLinkContainer {
display: flex;
align-items: center;
gap: 0.25rem;
}
.contestLink {
color: $color-text;
}
.backButton {
margin-top: 2rem;
}
</style>
7 changes: 4 additions & 3 deletions src/pages/UserAccounts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import BaseButton from '/@/components/UI/BaseButton.vue'
import AccountItem from '/@/components/UserAccounts/AccountItem.vue'
import { ref } from 'vue'
import apis from '/@/lib/apis'
import useUserDataFetcher from '/@/use/userDataFetcher'
import { RouterLink } from 'vue-router'
Copy link
Member

Choose a reason for hiding this comment

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

:kansya:

import { useFetcher } from '/@/use/fetcher'

const userId = ref('c714a848-2886-4c10-a313-de9bc61cb2bb')
// todo: get meが実装されたらそれを使う

const { data: accounts, fetcherState } = useUserDataFetcher(userId, userId =>
apis.getUserAccounts(userId)
const { data: accounts, fetcherState } = useFetcher(() =>
apis.getUserAccounts(userId.value)
)
</script>

Expand Down
6 changes: 3 additions & 3 deletions src/pages/Users.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import PageContainer from '/@/components/Layout/PageContainer.vue'
import UserProfile from '/@/components/Users/UserProfile.vue'
import UserProfileEdit from '/@/components/Users/UserProfileEdit.vue'
import apis from '/@/lib/apis'
import useUserDataFetcher from '/@/use/userDataFetcher'
import { useFetcher } from '/@/use/fetcher'

const userId = ref('c714a848-2886-4c10-a313-de9bc61cb2bb')
// todo: get meが実装されたらそれを使う

const { data: user, fetcherState } = useUserDataFetcher(userId, userId =>
apis.getUser(userId)
const { data: user, fetcherState } = useFetcher(() =>
apis.getUser(userId.value)
)
</script>

Expand Down
Loading