Files
sori.studio/components/admin/AdminPostExportRequestCard.vue

262 lines
9.1 KiB
Vue

<script setup>
/**
* 게시물 내보내기 요청 카드
* @property {boolean} expanded - 펼침 여부
* @property {string} dateRangeMode - 내보내기 범위 모드
* @property {number} year - 내보내기 연도
* @property {number} month - 내보내기 월
* @property {string} dateFrom - 직접 범위 시작일
* @property {string} dateTo - 직접 범위 종료일
* @property {number} maxFileSizeMb - 목표 ZIP 용량
* @property {number} chunkSize - ZIP당 최대 게시물 수
* @property {Array} dateRangeModeOptions - 범위 옵션
* @property {Array} yearOptions - 연도 옵션
* @property {Array} monthOptions - 월 옵션
* @property {boolean} canRequest - 요청 가능 여부
* @property {boolean} requesting - 요청 중 여부
* @property {boolean} hasActiveJobs - 진행 중 작업 여부
* @property {string} requestTitle - 요청 버튼 안내 문구
*/
defineProps({
expanded: {
type: Boolean,
default: false
},
dateRangeMode: {
type: String,
default: 'all'
},
year: {
type: [Number, String],
default: () => new Date().getFullYear()
},
month: {
type: [Number, String],
default: 1
},
dateFrom: {
type: String,
default: ''
},
dateTo: {
type: String,
default: ''
},
maxFileSizeMb: {
type: [Number, String],
default: 500
},
chunkSize: {
type: [Number, String],
default: 500
},
dateRangeModeOptions: {
type: Array,
default: () => []
},
yearOptions: {
type: Array,
default: () => []
},
monthOptions: {
type: Array,
default: () => []
},
canRequest: {
type: Boolean,
default: false
},
requesting: {
type: Boolean,
default: false
},
hasActiveJobs: {
type: Boolean,
default: false
},
requestTitle: {
type: String,
default: ''
}
})
const emit = defineEmits([
'toggle',
'request',
'update:dateRangeMode',
'update:year',
'update:month',
'update:dateFrom',
'update:dateTo',
'update:maxFileSizeMb',
'update:chunkSize'
])
/**
* 입력 이벤트 값을 문자열로 전달한다.
* @param {Event} event - 입력 이벤트
* @param {string} eventName - 전달할 이벤트 이름
* @returns {void}
*/
const emitTextInputValue = (event, eventName) => {
emit(eventName, event.target?.value || '')
}
/**
* 숫자 입력 이벤트 값을 전달한다.
* @param {Event} event - 입력 이벤트
* @param {string} eventName - 전달할 이벤트 이름
* @returns {void}
*/
const emitNumberInputValue = (event, eventName) => {
const value = event.target?.value ?? ''
emit(eventName, value === '' ? '' : Number(value))
}
</script>
<template>
<section
id="admin-settings-section-export"
class="admin-post-export-request-card admin-settings-screen__card rounded-xl border border-[#e6e8eb] bg-white p-6 shadow-[0_1px_2px_rgba(15,23,42,0.04)]"
>
<div class="admin-settings-screen__card-head flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
<div class="min-w-0">
<h2 class="text-lg font-semibold text-[#15171a]">
게시물 내보내기
</h2>
<p class="mt-1 text-sm leading-relaxed text-[#657080]">
게시물을 Obsidian 호환 ZIP 백업으로 만들고, 준비된 분할 파일을 내려받습니다.
</p>
</div>
<button
class="inline-flex h-10 shrink-0 cursor-pointer items-center justify-center rounded-md bg-[#15171a] px-4 text-sm font-semibold text-white transition hover:bg-black"
type="button"
:aria-expanded="expanded"
aria-controls="admin-settings-export-panel"
@click="emit('toggle')"
>
{{ expanded ? '접기' : '내보내기' }}
</button>
</div>
<div
v-if="expanded"
id="admin-settings-export-panel"
class="admin-post-export-request-card__actions admin-settings-screen__export-actions mt-4 grid gap-4 rounded-lg border border-[#e6e8eb] bg-[#fbfcfd] p-4"
>
<div class="min-w-0">
<p class="text-sm font-semibold text-[#15171a]">
Obsidian 호환 백업 준비
</p>
<p class="mt-1 text-sm leading-relaxed text-[#657080]">
전체·연도·월·직접 범위로 게시물을 골라 목표 용량 기준 ZIP 백업을 만듭니다.
</p>
</div>
<div class="grid gap-3">
<div class="admin-post-export-request-card__range admin-settings-screen__export-range grid gap-2 md:grid-cols-4">
<div class="grid gap-1 text-xs font-semibold text-[#5d6673]">
<span>범위</span>
<AdminSelectInput
:model-value="dateRangeMode"
:options="dateRangeModeOptions"
label="내보내기 범위"
select-class="admin-settings-screen__export-select h-10 rounded-md border-[#dce0e5] text-sm font-semibold text-[#15171a] focus:ring-1 focus:ring-[#15171a]"
width-class="w-full"
@update:model-value="emit('update:dateRangeMode', $event)"
/>
</div>
<div
v-if="dateRangeMode === 'year' || dateRangeMode === 'month'"
class="grid gap-1 text-xs font-semibold text-[#5d6673]"
>
<span>연도</span>
<AdminSelectInput
:model-value="year"
:options="yearOptions"
label="내보내기 연도"
select-class="admin-settings-screen__export-select h-10 rounded-md border-[#dce0e5] text-sm font-semibold text-[#15171a] focus:ring-1 focus:ring-[#15171a]"
width-class="w-full"
@update:model-value="emit('update:year', $event)"
/>
</div>
<div
v-if="dateRangeMode === 'month'"
class="grid gap-1 text-xs font-semibold text-[#5d6673]"
>
<span>월</span>
<AdminSelectInput
:model-value="month"
:options="monthOptions"
label="내보내기 월"
select-class="admin-settings-screen__export-select h-10 rounded-md border-[#dce0e5] text-sm font-semibold text-[#15171a] focus:ring-1 focus:ring-[#15171a]"
width-class="w-full"
@update:model-value="emit('update:month', $event)"
/>
</div>
<label
v-if="dateRangeMode === 'custom'"
class="grid gap-1 text-xs font-semibold text-[#5d6673]"
>
시작일
<input
:value="dateFrom"
class="h-10 rounded-md border border-[#dce0e5] bg-white px-3 text-sm font-semibold text-[#15171a] outline-none focus:border-[#15171a] focus:ring-1 focus:ring-[#15171a]"
type="date"
@input="emitTextInputValue($event, 'update:dateFrom')"
>
</label>
<label
v-if="dateRangeMode === 'custom'"
class="grid gap-1 text-xs font-semibold text-[#5d6673]"
>
종료일
<input
:value="dateTo"
class="h-10 rounded-md border border-[#dce0e5] bg-white px-3 text-sm font-semibold text-[#15171a] outline-none focus:border-[#15171a] focus:ring-1 focus:ring-[#15171a]"
type="date"
@input="emitTextInputValue($event, 'update:dateTo')"
>
</label>
</div>
<div class="admin-post-export-request-card__split admin-settings-screen__export-split grid gap-2 md:grid-cols-2">
<label class="grid gap-1 text-xs font-semibold text-[#5d6673]">
목표 ZIP 용량 (MB)
<input
:value="maxFileSizeMb"
class="h-10 rounded-md border border-[#dce0e5] bg-white px-3 text-sm font-semibold text-[#15171a] outline-none focus:border-[#15171a] focus:ring-1 focus:ring-[#15171a]"
type="number"
min="10"
max="2048"
step="10"
@input="emitNumberInputValue($event, 'update:maxFileSizeMb')"
>
</label>
<label class="grid gap-1 text-xs font-semibold text-[#5d6673]">
ZIP당 최대 게시물 수
<input
:value="chunkSize"
class="h-10 rounded-md border border-[#dce0e5] bg-white px-3 text-sm font-semibold text-[#15171a] outline-none focus:border-[#15171a] focus:ring-1 focus:ring-[#15171a]"
type="number"
min="1"
max="500"
step="1"
@input="emitNumberInputValue($event, 'update:chunkSize')"
>
</label>
</div>
<div class="flex items-center justify-end">
<button
class="admin-post-export-request-card__request admin-settings-screen__export-request inline-flex h-10 shrink-0 cursor-pointer items-center justify-center rounded-md bg-[#15171a] px-4 text-sm font-semibold text-white transition hover:bg-black disabled:cursor-not-allowed disabled:bg-[#c7cdd4]"
type="button"
:disabled="!canRequest"
:title="requestTitle"
@click="emit('request')"
>
{{ requesting ? '요청 중...' : hasActiveJobs ? '내보내기 진행 중' : '내보내기 요청' }}
</button>
</div>
</div>
</div>
</section>
</template>