340 lines
7.3 KiB
Vue
340 lines
7.3 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onUnmounted, ref, watch } from 'vue';
|
|
import { useEscClose } from '@/lib/modal-stack';
|
|
import type { MessageAutomationDelayUnit } from '@mento/shared';
|
|
import { toFaDigits } from '@/lib/jalali';
|
|
import UiIcon from '@/components/UiIcon.vue';
|
|
|
|
const value = defineModel<number>('value', { default: 0 });
|
|
const unit = defineModel<MessageAutomationDelayUnit>('unit', { default: 'days' });
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
allowImmediate?: boolean;
|
|
interval?: boolean;
|
|
}>(),
|
|
{
|
|
allowImmediate: false,
|
|
interval: false,
|
|
},
|
|
);
|
|
|
|
const GROUPS: {
|
|
unit: MessageAutomationDelayUnit;
|
|
title: string;
|
|
hintStart: string;
|
|
hintInterval: string;
|
|
max: number;
|
|
dense?: boolean;
|
|
}[] = [
|
|
{
|
|
unit: 'hours',
|
|
title: 'ساعت',
|
|
hintStart: 'از ۱ تا ۲۳ ساعت بعد',
|
|
hintInterval: 'هر ۱ تا ۲۳ ساعت یکبار',
|
|
max: 23,
|
|
dense: true,
|
|
},
|
|
{
|
|
unit: 'days',
|
|
title: 'روز',
|
|
hintStart: 'از ۱ تا ۶ روز بعد',
|
|
hintInterval: 'هر ۱ تا ۶ روز یکبار',
|
|
max: 6,
|
|
},
|
|
{
|
|
unit: 'weeks',
|
|
title: 'هفته',
|
|
hintStart: 'از ۱ تا ۳ هفته بعد',
|
|
hintInterval: 'هر ۱ تا ۳ هفته یکبار',
|
|
max: 3,
|
|
},
|
|
{
|
|
unit: 'months',
|
|
title: 'ماه',
|
|
hintStart: 'از ۱ تا ۱۱ ماه بعد',
|
|
hintInterval: 'هر ۱ تا ۱۱ ماه یکبار',
|
|
max: 11,
|
|
dense: true,
|
|
},
|
|
];
|
|
|
|
const UNIT_WORD: Record<MessageAutomationDelayUnit, string> = {
|
|
minutes: 'دقیقه',
|
|
hours: 'ساعت',
|
|
days: 'روز',
|
|
weeks: 'هفته',
|
|
months: 'ماه',
|
|
};
|
|
|
|
const open = ref(false);
|
|
|
|
const display = computed(() => {
|
|
if (props.allowImmediate && (!value.value || value.value <= 0)) return 'همان لحظه';
|
|
const n = Math.max(1, value.value || 1);
|
|
const word = UNIT_WORD[unit.value] ?? 'روز';
|
|
return props.interval
|
|
? `هر ${toFaDigits(n)} ${word} یکبار`
|
|
: `${toFaDigits(n)} ${word} بعد`;
|
|
});
|
|
|
|
const isSelected = (itemUnit: MessageAutomationDelayUnit, n: number) =>
|
|
unit.value === itemUnit && Number(value.value) === n;
|
|
|
|
function pick(itemUnit: MessageAutomationDelayUnit, n: number) {
|
|
value.value = n;
|
|
unit.value = itemUnit;
|
|
open.value = false;
|
|
}
|
|
|
|
function pickImmediate() {
|
|
value.value = 0;
|
|
unit.value = 'minutes';
|
|
open.value = false;
|
|
}
|
|
|
|
useEscClose(() => open.value, () => { open.value = false; });
|
|
|
|
watch(open, (isOpen) => {
|
|
document.body.style.overflow = isOpen ? 'hidden' : '';
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
document.body.style.overflow = '';
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="wrap">
|
|
<button class="trigger" type="button" :aria-expanded="open" @click="open = true">
|
|
<span>{{ display }}</span>
|
|
<UiIcon name="clock" :size="18" />
|
|
</button>
|
|
|
|
<Teleport to="body">
|
|
<div v-if="open" class="layer">
|
|
<div class="dim" aria-hidden="true" />
|
|
<section class="sheet" role="dialog" aria-modal="true" aria-label="انتخاب فاصله زمانی">
|
|
<header>
|
|
<div>
|
|
<h3>{{ interval ? 'فاصله بین پیامها' : 'شروع ارسال' }}</h3>
|
|
<p>
|
|
{{
|
|
interval
|
|
? 'پیام بعدی بعد از چند وقت برود؟'
|
|
: 'اولین پیام کی بعد از رویداد برود؟'
|
|
}}
|
|
</p>
|
|
</div>
|
|
<button class="x" type="button" aria-label="بستن" @click="open = false">
|
|
<UiIcon name="x" :size="18" />
|
|
</button>
|
|
</header>
|
|
|
|
<button
|
|
v-if="allowImmediate"
|
|
class="now"
|
|
type="button"
|
|
:class="{ on: !value }"
|
|
@click="pickImmediate"
|
|
>
|
|
<span>همان لحظه</span>
|
|
<small>بلافاصله بعد از رویداد</small>
|
|
</button>
|
|
|
|
<div v-for="group in GROUPS" :key="group.unit" class="group">
|
|
<div class="group-head">
|
|
<strong>{{ group.title }}</strong>
|
|
<span>{{ interval ? group.hintInterval : group.hintStart }}</span>
|
|
</div>
|
|
<div class="chips" :class="{ dense: group.dense }">
|
|
<button
|
|
v-for="n in group.max"
|
|
:key="`${group.unit}-${n}`"
|
|
class="chip"
|
|
type="button"
|
|
:class="{ on: isSelected(group.unit, n) }"
|
|
:aria-label="`${toFaDigits(n)} ${group.title}`"
|
|
@click="pick(group.unit, n)"
|
|
>
|
|
{{ toFaDigits(n) }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</Teleport>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.wrap,
|
|
.trigger {
|
|
width: 100%;
|
|
}
|
|
|
|
.trigger {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 0.5rem;
|
|
min-height: var(--control-h);
|
|
padding: 0 0.85rem;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: var(--radius);
|
|
background: #fff;
|
|
color: #0f172a;
|
|
font: inherit;
|
|
cursor: pointer;
|
|
text-align: start;
|
|
}
|
|
|
|
.layer {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 90;
|
|
display: grid;
|
|
place-items: end center;
|
|
}
|
|
|
|
.dim {
|
|
position: absolute;
|
|
inset: 0;
|
|
border: 0;
|
|
background: rgba(15, 23, 42, 0.32);
|
|
cursor: default;
|
|
}
|
|
|
|
.sheet {
|
|
position: relative;
|
|
z-index: 1;
|
|
width: min(36rem, 100%);
|
|
max-height: min(88dvh, 46rem);
|
|
overflow: auto;
|
|
padding: 1.15rem 1.2rem calc(1.2rem + env(safe-area-inset-bottom));
|
|
border-radius: 24px 24px 0 0;
|
|
background: #fff;
|
|
box-shadow: 0 -16px 40px rgba(15, 23, 42, 0.14);
|
|
}
|
|
|
|
header {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 0.8rem;
|
|
margin-bottom: 0.95rem;
|
|
}
|
|
|
|
h3 {
|
|
margin: 0;
|
|
font-size: 1.05rem;
|
|
}
|
|
|
|
header p {
|
|
margin: 0.22rem 0 0;
|
|
color: #94a3b8;
|
|
font-size: 0.82rem;
|
|
}
|
|
|
|
.x {
|
|
width: 2rem;
|
|
height: 2rem;
|
|
border: 0;
|
|
border-radius: 999px;
|
|
background: #f1f5f9;
|
|
color: #64748b;
|
|
display: grid;
|
|
place-items: center;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.now {
|
|
display: grid;
|
|
gap: 0.1rem;
|
|
width: 100%;
|
|
min-height: 3.1rem;
|
|
margin-bottom: 1rem;
|
|
padding: 0.55rem 0.9rem;
|
|
border: 1px solid #e8eef3;
|
|
border-radius: 14px;
|
|
background: #f8fafc;
|
|
color: #0f172a;
|
|
font: inherit;
|
|
font-weight: 800;
|
|
text-align: start;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.now small {
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
color: #94a3b8;
|
|
}
|
|
|
|
.group {
|
|
display: grid;
|
|
gap: 0.45rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.group-head {
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
gap: 0.6rem;
|
|
}
|
|
|
|
.group-head strong {
|
|
font-size: 0.9rem;
|
|
color: #0f172a;
|
|
}
|
|
|
|
.group-head span {
|
|
font-size: 0.75rem;
|
|
color: #94a3b8;
|
|
}
|
|
|
|
.chips {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(3.1rem, 1fr));
|
|
gap: 0.4rem;
|
|
}
|
|
|
|
.chips.dense {
|
|
grid-template-columns: repeat(auto-fill, minmax(2.55rem, 1fr));
|
|
}
|
|
|
|
.chip {
|
|
min-height: 2.55rem;
|
|
border: 1px solid #e8eef3;
|
|
border-radius: 12px;
|
|
background: #f8fafc;
|
|
color: #0f172a;
|
|
font: inherit;
|
|
font-weight: 800;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.now.on,
|
|
.chip.on {
|
|
background: #ecfdf8;
|
|
border-color: #99f6e4;
|
|
color: #0f766e;
|
|
}
|
|
|
|
.now.on small {
|
|
color: #0d9488;
|
|
}
|
|
|
|
@media (min-width: 760px) {
|
|
.layer {
|
|
place-items: center;
|
|
}
|
|
|
|
.sheet {
|
|
border-radius: 24px;
|
|
max-height: min(80vh, 44rem);
|
|
}
|
|
}
|
|
</style>
|