Skip to content

Commit

Permalink
Merge pull request #177 from vuejs-jp/feature/admin-exportcsv-spreads…
Browse files Browse the repository at this point in the history
…heets

feat: export csv - for admin
  • Loading branch information
jiyuujin authored Jul 4, 2024
2 parents b05d027 + 4906471 commit 185d4c5
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 4 deletions.
30 changes: 29 additions & 1 deletion apps/web/app/components/admin/Page.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
<script setup lang="ts">
import { useAsyncData } from '#imports'
import type { AdminPage } from '@vuejs-jp/model'
import { match } from 'ts-pattern'
import { ref } from 'vue'
import { useCsv } from '@vuejs-jp/composable'
import { useSupabase } from '~/composables/useSupabase'
import { useSupabaseCsv } from '~/composables/useSupabaseCsv'
import type AdminUserList from './AdminUserList.vue'
interface ListProps {
page: AdminPage
}
const { fetchData } = useSupabase()
const { exportSpeaker, exportSponsor, exportStaff } = useSupabaseCsv()
const { write } = useCsv()
const { data: speakers } = await useAsyncData('speakers', async () => {
return await fetchData('speakers')
})
Expand All @@ -28,6 +33,18 @@ const props = defineProps<ListProps>()
const showDialog = ref(false)
const handleDialog = () => showDialog.value = !showDialog.value
const handleCsv = async () => {
const res = await match(props.page)
.with('speaker', () => exportSpeaker('speakers'))
.with('sponsor', () => exportSponsor('sponsors'))
.with('adminUser', () => exportStaff('staffs'))
.with('namecard', () => null)
.exhaustive()
if (!res) return
write(res)
}
const pageText = props.page.replace(/^[a-z]/g, function (val) {
return val.toUpperCase()
})
Expand All @@ -40,13 +57,24 @@ const pageText = props.page.replace(/^[a-z]/g, function (val) {
<div>
<VFLinkButton
is="button"
v-if="page !== 'namecard'"
class="action"
background-color="white"
color="vue-blue"
@click="handleDialog"
>
{{ `Add ${pageText === 'AdminUser' ? 'staff' : pageText}` }}
</VFLinkButton>
<VFLinkButton
is="button"
v-if="page !== 'namecard'"
class="action"
background-color="white"
color="vue-blue"
@click="handleCsv"
>
{{ `Export ${pageText === 'AdminUser' ? 'staff' : pageText}` }}
</VFLinkButton>
<VFLinkButton
v-if="page === 'adminUser'"
href="/staff/invite"
Expand Down Expand Up @@ -88,7 +116,7 @@ const pageText = props.page.replace(/^[a-z]/g, function (val) {
width: 144px;
}
.tab-content-header button {
width: 144px;
width: 184px;
}
.tab-content-admin {
display: grid;
Expand Down
30 changes: 30 additions & 0 deletions apps/web/app/composables/useSupabaseCsv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useSupabaseClient } from '#imports'
import { type Table } from '@vuejs-jp/model'
import type { Database } from '~/types/generated/supabase'

export function useSupabaseCsv() {
const client = useSupabaseClient<Database>()

async function exportSpeaker(table: Extract<Table, 'speakers'>) {
const { data, error } = await client.from(table).select().csv()
if (error) return

return data
}

async function exportSponsor(table: Extract<Table, 'sponsors'>) {
const { data, error } = await client.from(table).select().csv()
if (error) return

return data
}

async function exportStaff(table: Extract<Table, 'staffs'>) {
const { data, error } = await client.from(table).select().csv()
if (error) return

return data
}

return { exportSpeaker, exportSponsor, exportStaff }
}
3 changes: 3 additions & 0 deletions apps/web/app/pages/staff/console.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ const { hasAuth } = useAuthSession()
<AdminPage page="sponsor" />
</template>
<template #tab_content_2>
<AdminPage page="namecard" />
</template>
<template #tab_content_3>
<AdminPage page="adminUser" />
</template>
</VFTab>
Expand Down
1 change: 1 addition & 0 deletions packages/composable/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './lib/useArray'
export * from './lib/useColor'
export * from './lib/useCsv'
export * from './lib/useHeader'
export * from './lib/useToast'
export * from './lib/useTypography'
17 changes: 17 additions & 0 deletions packages/composable/lib/useCsv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export function useCsv() {
function write(data: any) {
const blob = new Blob([data], { type: 'text/csv;charset=utf-8;' })
const link = document.createElement('a')
if (link.download !== undefined) {
const url = URL.createObjectURL(blob)
link.setAttribute('href', url)
link.setAttribute('download', 'data.csv')
link.style.visibility = 'hidden'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
}

return { write }
}
1 change: 1 addition & 0 deletions packages/model/lib/admin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const AdminPageMap = {
speaker: 'speaker',
sponsor: 'sponsor',
namecard: 'namecard',
adminUser: 'adminUser',
} as const

Expand Down
7 changes: 4 additions & 3 deletions packages/ui/components/tab/Tab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ function clickItem(index: number) {

<template>
<div class="tab">
<div v-for="(tab, index) in 3" :key="index">
<div v-for="(tab, index) in labels.length" :key="index">
<input :id="`tab_item_${index}`" type="radio" name="tab-item" :checked="selectedIndex === index" @change="clickItem(index)" />
<label class="tab-item" :for="`tab_item_${index}`">{{ labels[index] }}</label>
<label class="tab-item" :for="`tab_item_${index}`" :style="{ width: `calc(100% / ${labels.length})` }">
{{ labels[index] }}
</label>
</div>
<slot :name="`tab_content_${selectedIndex}`" />
</div>
Expand All @@ -32,7 +34,6 @@ function clickItem(index: number) {
margin: 0 auto;
}
.tab-item {
width: calc(100%/3);
height: calc(var(--unit) * 6.25);
border-bottom: 3px solid var(--color-vue-green);
background-color: var(--color-white);
Expand Down

0 comments on commit 185d4c5

Please sign in to comment.