-
Notifications
You must be signed in to change notification settings - Fork 0
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
Contestの詳細ページの作成 #51
Changes from 11 commits
24d3754
8f6b417
2532054
94f2e16
5d3c3ef
1e23302
927f2c8
4ef03e9
00c440a
39f6cda
e599df3
545b07a
6b1c179
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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> |
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; | ||
} | ||
.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> |
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> |
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)" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 絞り込んだあとのチームを表示する責任があるのは、
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. サーバーで検索機能実装してもらえたら There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. そしたら、 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ほんとですね... |
||
/> | ||
</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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
.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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
使ってなさそう