create-m2m-bo-template-vue 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/src/pages/b2c/products/B2cProductCreatePage.vue +9 -9
- package/template/src/pages/c2c/sales/SalesPerformancePage.vue +1 -1
- package/template/src/pages/components/ComponentsPage.vue +22 -24
- package/template/src/pages/content/CategoriesPage.vue +1 -1
- package/template/src/pages/content/ContentListPage.vue +2 -3
- package/template/src/pages/content/ExposurePage.vue +2 -2
- package/template/src/pages/content/ReviewPage.vue +1 -1
- package/template/src/pages/content/TagsPage.vue +1 -1
- package/template/src/pages/coupons/CouponPolicyPage.vue +9 -9
- package/template/src/pages/cs/AdminMemoDrawer.vue +4 -3
- package/template/src/pages/cs/ConsultationDetailDrawer.vue +3 -1
- package/template/src/pages/cs/ConsultationsPage.vue +5 -1
- package/template/src/pages/cs/CsHistoryDetailDrawer.vue +6 -2
- package/template/src/pages/cs/ProductInquiryDetailDrawer.vue +6 -2
- package/template/src/pages/delivery/DeliveryListPage.vue +1 -1
- package/template/src/pages/inventory/InventoryAlertsConfigPage.vue +13 -9
- package/template/src/pages/inventory/StockMovementManagementPage.vue +8 -4
- package/template/src/pages/logs/ApiLogDetailDrawer.vue +83 -0
- package/template/src/pages/logs/ErrorGroupDetailDrawer.vue +74 -0
- package/template/src/pages/logs/SecurityLogDetailDrawer.vue +116 -0
- package/template/src/pages/logs/SecurityLogPage.module.css +2 -0
- package/template/src/pages/logs/SecurityLogPage.vue +224 -0
- package/template/src/pages/logs/SystemLogPage.module.css +130 -0
- package/template/src/pages/logs/SystemLogPage.vue +346 -0
- package/template/src/pages/logs/securityLogData.ts +39 -0
- package/template/src/pages/logs/systemLogData.ts +160 -0
- package/template/src/pages/notifications/NotificationDispatchPage.vue +6 -5
- package/template/src/pages/orders/RefundPage.vue +6 -5
- package/template/src/pages/points/PointPolicyPage.vue +8 -8
- package/template/src/pages/promotions/PromotionStackSettingPage.vue +2 -2
- package/template/src/pages/stats/useStatisticsPeriod.ts +10 -0
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, h,
|
|
2
|
+
import { computed, h, onUnmounted, reactive, ref, watch, type VNodeChild } from 'vue';
|
|
3
3
|
import { useRouter } from 'vue-router';
|
|
4
4
|
import { CheckCircle2, ChevronDown, ChevronUp, Circle, CircleAlert, ImagePlus, Plus, X, XCircle } from 'lucide-vue-next';
|
|
5
5
|
import styles from './B2cProductCreatePage.module.css';
|
|
@@ -379,7 +379,7 @@ function onFileInputChange(event: Event) {
|
|
|
379
379
|
size="sm"
|
|
380
380
|
:class="styles.fieldAside"
|
|
381
381
|
:model-value="form.autoCode"
|
|
382
|
-
@update:model-value="(checked: boolean) => { form.autoCode =
|
|
382
|
+
@update:model-value="(checked: boolean | string[]) => { const c = checked as boolean; form.autoCode = c; if (c) form.code = createInitialForm().code; }"
|
|
383
383
|
>
|
|
384
384
|
자동 생성
|
|
385
385
|
</Checkbox>
|
|
@@ -391,7 +391,7 @@ function onFileInputChange(event: Event) {
|
|
|
391
391
|
placeholder="브랜드명 검색"
|
|
392
392
|
:options="BRAND_OPTIONS"
|
|
393
393
|
:model-value="form.brand"
|
|
394
|
-
@update:model-value="(v: string) => update('brand', v)"
|
|
394
|
+
@update:model-value="(v: string | string[]) => update('brand', v as string)"
|
|
395
395
|
/>
|
|
396
396
|
<span :class="styles.helperText">등록된 브랜드만 선택할 수 있습니다.</span>
|
|
397
397
|
</label>
|
|
@@ -405,7 +405,7 @@ function onFileInputChange(event: Event) {
|
|
|
405
405
|
placeholder="대분류 선택"
|
|
406
406
|
:options="B2C_CATEGORIES.map((n) => ({ value: n.id, label: n.name }))"
|
|
407
407
|
:model-value="form.cat1"
|
|
408
|
-
@update:model-value="(v: string) => { form.cat1 = v; form.cat2 = ''; form.cat3 = ''; }"
|
|
408
|
+
@update:model-value="(v: string | string[]) => { form.cat1 = v as string; form.cat2 = ''; form.cat3 = ''; }"
|
|
409
409
|
/>
|
|
410
410
|
<Select
|
|
411
411
|
aria-label="중분류"
|
|
@@ -413,7 +413,7 @@ function onFileInputChange(event: Event) {
|
|
|
413
413
|
:disabled="level2Options.length === 0"
|
|
414
414
|
:options="level2Options.map((n) => ({ value: n.id, label: n.name }))"
|
|
415
415
|
:model-value="form.cat2"
|
|
416
|
-
@update:model-value="(v: string) => { form.cat2 = v; form.cat3 = ''; }"
|
|
416
|
+
@update:model-value="(v: string | string[]) => { form.cat2 = v as string; form.cat3 = ''; }"
|
|
417
417
|
/>
|
|
418
418
|
<Select
|
|
419
419
|
aria-label="소분류"
|
|
@@ -421,7 +421,7 @@ function onFileInputChange(event: Event) {
|
|
|
421
421
|
:disabled="level3Options.length === 0"
|
|
422
422
|
:options="level3Options.map((n) => ({ value: n.id, label: n.name }))"
|
|
423
423
|
:model-value="form.cat3"
|
|
424
|
-
@update:model-value="(v: string) => update('cat3', v)"
|
|
424
|
+
@update:model-value="(v: string | string[]) => update('cat3', v as string)"
|
|
425
425
|
/>
|
|
426
426
|
</div>
|
|
427
427
|
<span v-if="path.length === 3" :class="styles.helperText">{{ path.join(' > ') }}</span>
|
|
@@ -525,7 +525,7 @@ function onFileInputChange(event: Event) {
|
|
|
525
525
|
:items="form.extraImages"
|
|
526
526
|
@change="(next: typeof form.extraImages) => update('extraImages', next)"
|
|
527
527
|
>
|
|
528
|
-
<template #
|
|
528
|
+
<template #default="{ item: image, index }">
|
|
529
529
|
<div :class="styles.imageTile">
|
|
530
530
|
<div :class="styles.tileTop">
|
|
531
531
|
<span :class="styles.tileNum">{{ index + 1 }}</span>
|
|
@@ -663,7 +663,7 @@ function onFileInputChange(event: Event) {
|
|
|
663
663
|
|
|
664
664
|
<div :class="styles.stockFooter">
|
|
665
665
|
<span :class="styles.muted">재고 수량은 [상품 관리 > 재고 관리]에서 관리합니다. 등록 편의를 위한 초기 재고만 여기서 입력할 수 있습니다.</span>
|
|
666
|
-
<Checkbox size="sm" :model-value="form.withStock" @update:model-value="(v: boolean) => update('withStock', v)">초기 재고 함께 입력</Checkbox>
|
|
666
|
+
<Checkbox size="sm" :model-value="form.withStock" @update:model-value="(v: boolean | string[]) => update('withStock', v as boolean)">초기 재고 함께 입력</Checkbox>
|
|
667
667
|
</div>
|
|
668
668
|
</div>
|
|
669
669
|
</Card>
|
|
@@ -716,7 +716,7 @@ function onFileInputChange(event: Event) {
|
|
|
716
716
|
<div :class="styles.stack">
|
|
717
717
|
<label :class="styles.fieldLabel">
|
|
718
718
|
배송 정책 <span class="req">*</span>
|
|
719
|
-
<Select :options="SHIPPING_POLICIES.map(({ value, label }) => ({ value, label }))" :model-value="form.shippingPolicy" placeholder="배송 정책 선택" @update:model-value="(v: string) => update('shippingPolicy', v)" />
|
|
719
|
+
<Select :options="SHIPPING_POLICIES.map(({ value, label }) => ({ value, label }))" :model-value="form.shippingPolicy" placeholder="배송 정책 선택" @update:model-value="(v: string | string[]) => update('shippingPolicy', v as string)" />
|
|
720
720
|
</label>
|
|
721
721
|
<Notice v-if="policy" type="neutral" :description="policy.summary" action-text="정책 관리로 이동" @action="router.push('/delivery-policy/base-fee')" />
|
|
722
722
|
<div :class="styles.inlineFields">
|
|
@@ -100,7 +100,7 @@ function downloadPerformance() {
|
|
|
100
100
|
<div :class="styles.donutArea">
|
|
101
101
|
<StatisticsDonutChart
|
|
102
102
|
:data="categoryTotals.map((item) => ({ label: item.category, value: item.value }))"
|
|
103
|
-
:center-value="SALE_PRODUCTS.length"
|
|
103
|
+
:center-value="`${SALE_PRODUCTS.length}`"
|
|
104
104
|
center-label="상품"
|
|
105
105
|
:max-legend-items="4"
|
|
106
106
|
aria-label="카테고리 거래 구성"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, ref } from 'vue';
|
|
3
|
-
import { Plus, CheckCircle
|
|
3
|
+
import { Plus, CheckCircle } from 'lucide-vue-next';
|
|
4
4
|
import { Button } from 'm2m-uiux-vue/Button';
|
|
5
5
|
import { ButtonGroup } from 'm2m-uiux-vue/ButtonGroup';
|
|
6
6
|
import { Segmented } from 'm2m-uiux-vue/Segmented';
|
|
@@ -12,7 +12,7 @@ import { RadioGroup } from 'm2m-uiux-vue/RadioGroup';
|
|
|
12
12
|
import { Switch } from 'm2m-uiux-vue/Switch';
|
|
13
13
|
import { Textarea } from 'm2m-uiux-vue/Textarea';
|
|
14
14
|
import { Divider } from 'm2m-uiux-vue/Divider';
|
|
15
|
-
import { Badge } from 'm2m-uiux-vue/Badge';
|
|
15
|
+
import { Badge, BADGE_TYPE } from 'm2m-uiux-vue/Badge';
|
|
16
16
|
import { Header as M2MHeader } from 'm2m-uiux-vue/Header';
|
|
17
17
|
import { Form } from 'm2m-uiux-vue/Form';
|
|
18
18
|
import { Table, type TableColumn } from 'm2m-uiux-vue/Table';
|
|
@@ -111,11 +111,11 @@ const switchOn = ref(true);
|
|
|
111
111
|
|
|
112
112
|
const tableSel = ref<Array<string | number>>([]);
|
|
113
113
|
const tabValue = ref('all');
|
|
114
|
-
const segmentTab = ref('
|
|
114
|
+
const segmentTab = ref('B2C');
|
|
115
115
|
const stepIndex = ref(1);
|
|
116
116
|
const progress = ref(62);
|
|
117
117
|
|
|
118
|
-
const dateSingle = ref<
|
|
118
|
+
const dateSingle = ref<Date | null>(null);
|
|
119
119
|
const timeValue = ref('');
|
|
120
120
|
const legacyDate = ref('');
|
|
121
121
|
|
|
@@ -123,10 +123,10 @@ const gridSearch = ref('');
|
|
|
123
123
|
const chartHeight = ref(220);
|
|
124
124
|
const previewNavOpen = ref(true);
|
|
125
125
|
|
|
126
|
-
const tableColumns: TableColumn<
|
|
127
|
-
{ key: 'name',
|
|
128
|
-
{ key: 'status',
|
|
129
|
-
{ key: 'amount',
|
|
126
|
+
const tableColumns: TableColumn<Record<string,any>>[] = [
|
|
127
|
+
{ key: 'name', label: '이름' },
|
|
128
|
+
{ key: 'status', label: '상태' },
|
|
129
|
+
{ key: 'amount', label: '금액', align: 'right' },
|
|
130
130
|
];
|
|
131
131
|
|
|
132
132
|
const gridColumns: GridColumn[] = [{ label: '이름' }, { label: '상태' }, { label: '금액', align: 'right' }];
|
|
@@ -214,7 +214,7 @@ const presetItems = [
|
|
|
214
214
|
<div :class="[styles.demoBody, styles.col]">
|
|
215
215
|
<StatGrid :columns="4">
|
|
216
216
|
<StatCard label="당월 총 매출" value="₩128,450,000" value-color="#18181b" note="+14.2% 전월 대비 상승" tone="up">
|
|
217
|
-
<template #badge><Badge type="
|
|
217
|
+
<template #badge><Badge :type="BADGE_TYPE['SUCCESS']" size="sm">성장</Badge></template>
|
|
218
218
|
</StatCard>
|
|
219
219
|
<StatCard label="신규 반품 요청" value="23건" value-color="#dc2626" note="-3건 전주 대비 감소" tone="down" dot="#ef4444" />
|
|
220
220
|
<StatCard label="미처리 정산 건" value="12건" note="마감 D-2 예정" tone="warn" dot="#f59e0b" />
|
|
@@ -503,12 +503,11 @@ const presetItems = [
|
|
|
503
503
|
<div :class="styles.demo">
|
|
504
504
|
<div :class="styles.demoHead"><span :class="styles.demoName">Badge</span><span :class="styles.demoDesc">운영 화면에서 사용하는 상태 색상</span></div>
|
|
505
505
|
<div :class="styles.demoBody">
|
|
506
|
-
<Badge type="
|
|
507
|
-
<Badge type="
|
|
508
|
-
<Badge type="
|
|
509
|
-
<Badge type="
|
|
510
|
-
<Badge type="
|
|
511
|
-
<Badge type="neutral">중지</Badge>
|
|
506
|
+
<Badge :type="BADGE_TYPE['SUCCESS']">정상</Badge>
|
|
507
|
+
<Badge :type="BADGE_TYPE['INFO']">진행중</Badge>
|
|
508
|
+
<Badge :type="BADGE_TYPE['PRIMARY']">예약</Badge>
|
|
509
|
+
<Badge :type="BADGE_TYPE['WARNING']">주의</Badge>
|
|
510
|
+
<Badge :type="BADGE_TYPE['ERROR']">실패</Badge>
|
|
512
511
|
</div>
|
|
513
512
|
</div>
|
|
514
513
|
<div :class="styles.demo">
|
|
@@ -520,7 +519,7 @@ const presetItems = [
|
|
|
520
519
|
<div :class="styles.demo">
|
|
521
520
|
<div :class="styles.demoHead"><span :class="styles.demoName">Form / FormField</span></div>
|
|
522
521
|
<div :class="[styles.demoBody, styles.col]">
|
|
523
|
-
<Form
|
|
522
|
+
<Form>
|
|
524
523
|
<Form.Field label="담당자" required>
|
|
525
524
|
<Input placeholder="이름" />
|
|
526
525
|
</Form.Field>
|
|
@@ -611,15 +610,14 @@ const presetItems = [
|
|
|
611
610
|
</div>
|
|
612
611
|
<div :class="[styles.demoBody, styles.col]">
|
|
613
612
|
<div style="display: flex; flex-direction: column; gap: 14px">
|
|
614
|
-
<div style="display: flex; flex-direction: column; gap: 6px">
|
|
613
|
+
<div style="display: inline-flex; flex-direction: column; gap: 6px; width: min-content;">
|
|
615
614
|
<span style="font-size: 0.71875rem; font-weight: 700; color: var(--common-text-muted)">세그먼트형 (type="segmented") — 유형 전환 / 토글 탭</span>
|
|
616
|
-
<
|
|
617
|
-
type="segmented"
|
|
615
|
+
<Segmented
|
|
618
616
|
v-model="segmentTab"
|
|
619
|
-
:
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
617
|
+
:options="[
|
|
618
|
+
'B2C',
|
|
619
|
+
'C2C',
|
|
620
|
+
'B2B',
|
|
623
621
|
]"
|
|
624
622
|
/>
|
|
625
623
|
</div>
|
|
@@ -732,7 +730,7 @@ const presetItems = [
|
|
|
732
730
|
{ label: '도서/음반', value: 310000 },
|
|
733
731
|
{ label: '기타 잡화', value: 150000 },
|
|
734
732
|
]"
|
|
735
|
-
:center-value="186"
|
|
733
|
+
:center-value="'186'"
|
|
736
734
|
center-label="상품"
|
|
737
735
|
:max-legend-items="4"
|
|
738
736
|
aria-label="카테고리 구성비 차트 예제"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed,
|
|
2
|
+
import { computed, onUnmounted, ref } from 'vue';
|
|
3
3
|
import { useRouter } from 'vue-router';
|
|
4
4
|
import sh from './contentShared.module.css';
|
|
5
5
|
import styles from './CategoriesPage.module.css';
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, onMounted, onUnmounted,
|
|
2
|
+
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
|
3
3
|
import { useRoute } from 'vue-router';
|
|
4
4
|
import { PageSizeSelect } from '../../components/common';
|
|
5
|
-
import ExcelDownloadButton from '../../components/common/ExcelDownloadButton.vue';
|
|
6
5
|
import DatePicker from '../../components/forms/DatePicker.vue';
|
|
7
6
|
import { SearchField } from '../../components/SearchField';
|
|
8
7
|
import { ChevronDown, ChevronUp, RotateCcw, SlidersHorizontal } from 'lucide-vue-next';
|
|
@@ -107,7 +106,7 @@ const filtered = computed(() => {
|
|
|
107
106
|
const query = q.value.trim().toLowerCase();
|
|
108
107
|
let list = modeData.value.filter((r) => {
|
|
109
108
|
if (query) {
|
|
110
|
-
const hay =
|
|
109
|
+
const hay = r.id + r.title + r.author;
|
|
111
110
|
if (!hay.toLowerCase().includes(query)) return false;
|
|
112
111
|
}
|
|
113
112
|
if (status.value !== '전체' && r.status !== status.value) return false;
|
|
@@ -116,7 +116,7 @@ function runNavDialog(b: NavDialogButton) {
|
|
|
116
116
|
if (!navDialog.value) return;
|
|
117
117
|
if (b.act === 'close') { navDialog.value = null; return; }
|
|
118
118
|
if (b.act === 'gonosave') { changes.value = 0; area.value = navDialog.value.toId; selRowId.value = null; rowDraft.value = null; navDialog.value = null; }
|
|
119
|
-
if (b.act === 'gosave') { changes.value = 0; area.value = navDialog.value
|
|
119
|
+
if (b.act === 'gosave') { changes.value = 0; area.value = navDialog.value?.toId as string; selRowId.value = null; rowDraft.value = null; navDialog.value = null; toast.value = '노출 설정이 저장되었습니다.'; }
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
function openRow(entry: ExposureEntry) {
|
|
@@ -443,7 +443,7 @@ function toggleAreaOpen(id: string) {
|
|
|
443
443
|
<div :class="sh.metaRow">
|
|
444
444
|
<div :class="sh.metaCell">
|
|
445
445
|
<div :class="sh.metaCellLabel">현재 순서</div>
|
|
446
|
-
<div :class="sh.metaCellValue">{{ curList.findIndex((x) => x.id === selEntry
|
|
446
|
+
<div :class="sh.metaCellValue">{{ curList.findIndex((x) => x.id === selEntry?.id) + 1 }}</div>
|
|
447
447
|
</div>
|
|
448
448
|
<div :class="sh.metaCell">
|
|
449
449
|
<div :class="sh.metaCellLabel">등록 관리자</div>
|
|
@@ -64,7 +64,7 @@ const list = computed(() => {
|
|
|
64
64
|
let out = modeItems.value.filter((it) => {
|
|
65
65
|
if (tab.value !== '전체' && it.status !== tab.value) return false;
|
|
66
66
|
if (query) {
|
|
67
|
-
const hay =
|
|
67
|
+
const hay = it.ctid + it.title + it.requester;
|
|
68
68
|
if (!hay.toLowerCase().includes(query)) return false;
|
|
69
69
|
}
|
|
70
70
|
if (reqTypeF.value !== '전체' && it.reqType !== reqTypeF.value) return false;
|
|
@@ -97,7 +97,7 @@ const list = computed(() => {
|
|
|
97
97
|
let out = items.value.filter((it) => {
|
|
98
98
|
if (it.scope !== scope.value) return false;
|
|
99
99
|
if (query) {
|
|
100
|
-
const hay =
|
|
100
|
+
const hay = it.name + it.code;
|
|
101
101
|
if (!hay.toLowerCase().includes(query)) return false;
|
|
102
102
|
}
|
|
103
103
|
if (useF.value !== '전체' && (useF.value === '사용' ? !it.use : it.use)) return false;
|
|
@@ -294,9 +294,9 @@ const digest = computed<[string, string][]>(() => [
|
|
|
294
294
|
</div>
|
|
295
295
|
|
|
296
296
|
<div :class="styles.checkGrid">
|
|
297
|
-
<Checkbox size="md" label="동일 쿠폰 여러 장" :model-value="p.allowSameCouponMultiple" @update:model-value="(checked
|
|
298
|
-
<Checkbox size="md" label="프로모션과 중복" :model-value="p.promotionStackDefault" @update:model-value="(checked
|
|
299
|
-
<Checkbox size="md" label="포인트와 중복" :model-value="p.pointStackAllowed" @update:model-value="(checked
|
|
297
|
+
<Checkbox size="md" label="동일 쿠폰 여러 장" :model-value="p.allowSameCouponMultiple" @update:model-value="(checked) => set('allowSameCouponMultiple', checked as boolean)" />
|
|
298
|
+
<Checkbox size="md" label="프로모션과 중복" :model-value="p.promotionStackDefault" @update:model-value="(checked) => set('promotionStackDefault', checked as boolean)" />
|
|
299
|
+
<Checkbox size="md" label="포인트와 중복" :model-value="p.pointStackAllowed" @update:model-value="(checked) => set('pointStackAllowed', checked as boolean)" />
|
|
300
300
|
</div>
|
|
301
301
|
</div>
|
|
302
302
|
</div>
|
|
@@ -322,7 +322,7 @@ const digest = computed<[string, string][]>(() => [
|
|
|
322
322
|
<div>
|
|
323
323
|
<div :class="styles.fieldLabel">포함 항목</div>
|
|
324
324
|
<div :class="styles.checkRow">
|
|
325
|
-
<Checkbox size="md" label="배송비 포함" :model-value="p.includeShippingInMin" @update:model-value="(checked
|
|
325
|
+
<Checkbox size="md" label="배송비 포함" :model-value="p.includeShippingInMin" @update:model-value="(checked) => set('includeShippingInMin', checked as boolean)" />
|
|
326
326
|
</div>
|
|
327
327
|
</div>
|
|
328
328
|
</div>
|
|
@@ -348,23 +348,23 @@ const digest = computed<[string, string][]>(() => [
|
|
|
348
348
|
</div>
|
|
349
349
|
<div :class="styles.tableRow">
|
|
350
350
|
<span :class="styles.tableRowLabel">전체 취소</span>
|
|
351
|
-
<span :class="styles.tableCellCenter"><Checkbox size="sm" aria-label="전체 취소 시 쿠폰 복원" :model-value="p.fullCancelRestore" @update:model-value="(checked
|
|
351
|
+
<span :class="styles.tableCellCenter"><Checkbox size="sm" aria-label="전체 취소 시 쿠폰 복원" :model-value="p.fullCancelRestore" @update:model-value="(checked) => set('fullCancelRestore', checked as boolean)" /></span>
|
|
352
352
|
<span :class="styles.tableCellCenter"><Checkbox size="sm" aria-label="전체 취소 시 잔여 재계산" :model-value="false" disabled /></span>
|
|
353
353
|
</div>
|
|
354
354
|
<div :class="styles.tableRow">
|
|
355
355
|
<span :class="styles.tableRowLabel">전체 반품</span>
|
|
356
|
-
<span :class="styles.tableCellCenter"><Checkbox size="sm" aria-label="전체 반품 시 쿠폰 복원" :model-value="p.fullRefundRestore" @update:model-value="(checked
|
|
356
|
+
<span :class="styles.tableCellCenter"><Checkbox size="sm" aria-label="전체 반품 시 쿠폰 복원" :model-value="p.fullRefundRestore" @update:model-value="(checked) => set('fullRefundRestore', checked as boolean)" /></span>
|
|
357
357
|
<span :class="styles.tableCellCenter"><Checkbox size="sm" aria-label="전체 반품 시 잔여 재계산" :model-value="false" disabled /></span>
|
|
358
358
|
</div>
|
|
359
359
|
<div :class="styles.tableRow">
|
|
360
360
|
<span :class="styles.tableRowLabel">부분 취소</span>
|
|
361
361
|
<span :class="styles.tableCellCenter"><Checkbox size="sm" aria-label="부분 취소 시 쿠폰 복원" :model-value="false" disabled /></span>
|
|
362
|
-
<span :class="styles.tableCellCenter"><Checkbox size="sm" aria-label="부분 취소 시 잔여 재계산" :model-value="p.partialCancelRecalculate" @update:model-value="(checked
|
|
362
|
+
<span :class="styles.tableCellCenter"><Checkbox size="sm" aria-label="부분 취소 시 잔여 재계산" :model-value="p.partialCancelRecalculate" @update:model-value="(checked) => set('partialCancelRecalculate', checked as boolean)" /></span>
|
|
363
363
|
</div>
|
|
364
364
|
<div :class="styles.tableRow">
|
|
365
365
|
<span :class="styles.tableRowLabel">부분 반품</span>
|
|
366
366
|
<span :class="styles.tableCellCenter"><Checkbox size="sm" aria-label="부분 반품 시 쿠폰 복원" :model-value="false" disabled /></span>
|
|
367
|
-
<span :class="styles.tableCellCenter"><Checkbox size="sm" aria-label="부분 반품 시 잔여 재계산" :model-value="p.partialRefundRecalculate" @update:model-value="(checked
|
|
367
|
+
<span :class="styles.tableCellCenter"><Checkbox size="sm" aria-label="부분 반품 시 잔여 재계산" :model-value="p.partialRefundRecalculate" @update:model-value="(checked) => set('partialRefundRecalculate', checked as boolean)" /></span>
|
|
368
368
|
</div>
|
|
369
369
|
</div>
|
|
370
370
|
</div>
|
|
@@ -410,7 +410,7 @@ const digest = computed<[string, string][]>(() => [
|
|
|
410
410
|
<div>
|
|
411
411
|
<div :class="styles.fieldLabel">총 한도 초과 시</div>
|
|
412
412
|
<div :class="styles.checkRow">
|
|
413
|
-
<Checkbox size="md" label="발급 차단" :model-value="p.blockOnLimitExceeded" @update:model-value="(checked
|
|
413
|
+
<Checkbox size="md" label="발급 차단" :model-value="p.blockOnLimitExceeded" @update:model-value="(checked) => set('blockOnLimitExceeded', checked as boolean)" />
|
|
414
414
|
</div>
|
|
415
415
|
</div>
|
|
416
416
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { DatePicker } from '../../components/forms';
|
|
3
|
+
import { useRouter } from 'vue-router';
|
|
3
4
|
import { computed, ref } from 'vue';
|
|
4
5
|
import drawer from '../ops/opsDrawerShared.module.css';
|
|
5
6
|
import styles from './AdminMemosPage.module.css';
|
|
@@ -7,7 +8,7 @@ import { useOutsideClose } from '../../lib/useOutsideClose';
|
|
|
7
8
|
import { MEMO_ADMINS, MEMO_TARGET_TYPES, MEMO_TYPES, followUpState, type AdminMemoEntry, type MemoTargetType, type MemoType, type PinScope } from './adminMemosData';
|
|
8
9
|
|
|
9
10
|
type Tab = 'memo' | 'context' | 'followup' | 'version' | 'history';
|
|
10
|
-
|
|
11
|
+
const router = useRouter()
|
|
11
12
|
const props = defineProps<{ initial: AdminMemoEntry; isNew: boolean; startEditing?: boolean }>();
|
|
12
13
|
const emit = defineEmits<{
|
|
13
14
|
close: [];
|
|
@@ -148,8 +149,8 @@ function handleFollowUpDateChange(e: Event) {
|
|
|
148
149
|
</div>
|
|
149
150
|
<section :class="styles.formSection">
|
|
150
151
|
<h3>연결 업무</h3>
|
|
151
|
-
<div v-if="draft.relatedInquiryId" :class="styles.contextRow"><span>1:1 문의</span><strong>{{ draft.relatedInquiryId }}</strong><em>연결됨</em><button type="button" @click="() => (
|
|
152
|
-
<div v-if="draft.relatedConsultationId" :class="styles.contextRow"><span>상담</span><strong>{{ draft.relatedConsultationId }}</strong><em>연결됨</em><button type="button" @click="() => (
|
|
152
|
+
<div v-if="draft.relatedInquiryId" :class="styles.contextRow"><span>1:1 문의</span><strong>{{ draft.relatedInquiryId }}</strong><em>연결됨</em><button type="button" @click="() => router.push('/cs/inquiries')">보기 ↗</button></div>
|
|
153
|
+
<div v-if="draft.relatedConsultationId" :class="styles.contextRow"><span>상담</span><strong>{{ draft.relatedConsultationId }}</strong><em>연결됨</em><button type="button" @click="() => router.push('/cs/consultations')">보기 ↗</button></div>
|
|
153
154
|
<div v-if="draft.relatedOrderId" :class="styles.contextRow"><span>주문</span><strong>{{ draft.relatedOrderId }}</strong><em>연결됨</em><button type="button">보기 ↗</button></div>
|
|
154
155
|
<div v-if="!draft.relatedInquiryId && !draft.relatedConsultationId && !draft.relatedOrderId" :class="drawer.emptyInline">추가로 연결된 업무가 없습니다.</div>
|
|
155
156
|
</section>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, ref } from 'vue';
|
|
3
|
+
import { useRouter } from 'vue-router';
|
|
3
4
|
import drawer from '../ops/opsDrawerShared.module.css';
|
|
4
5
|
import styles from './ConsultationsPage.module.css';
|
|
5
6
|
import { useOutsideClose } from '../../lib/useOutsideClose';
|
|
@@ -7,6 +8,7 @@ import { durationLabel, type ConsultationEntry } from './consultationsData';
|
|
|
7
8
|
|
|
8
9
|
type Tab = 'summary' | 'conversation' | 'related' | 'result' | 'memo' | 'history';
|
|
9
10
|
|
|
11
|
+
const router = useRouter()
|
|
10
12
|
const props = defineProps<{ item: ConsultationEntry }>();
|
|
11
13
|
const emit = defineEmits<{
|
|
12
14
|
close: [];
|
|
@@ -59,7 +61,7 @@ function styleKey(prefix: string, key: string) {
|
|
|
59
61
|
</div>
|
|
60
62
|
<div :class="drawer.actionRow">
|
|
61
63
|
<button type="button" :class="drawer.actionLink">고객 보기 ↗</button>
|
|
62
|
-
<button v-if="item.inquiryId" type="button" :class="drawer.actionLink" @click="() => (
|
|
64
|
+
<button v-if="item.inquiryId" type="button" :class="drawer.actionLink" @click="() => (router.push('/cs/inquiries'))">원 문의 보기 ↗</button>
|
|
63
65
|
<button type="button" :class="drawer.actionLink" @click="emit('correct')">상담 기록 정정</button>
|
|
64
66
|
<span :class="drawer.spacer" />
|
|
65
67
|
<button type="button" :class="drawer.actionLink" @click="emit('newInquiry')">새 문의 등록</button>
|
|
@@ -225,6 +225,10 @@ function register() {
|
|
|
225
225
|
toastBriefly('상담 내역을 등록했습니다.');
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
function goToInquiries() {
|
|
229
|
+
window.location.href = '/cs/inquiries';
|
|
230
|
+
}
|
|
231
|
+
|
|
228
232
|
function reopenInquiry() {
|
|
229
233
|
if (!detail.value) return;
|
|
230
234
|
const current = detail.value;
|
|
@@ -473,7 +477,7 @@ const rows = computed<GridRow[]>(() =>
|
|
|
473
477
|
@close="detailId = null"
|
|
474
478
|
@reopen="modal = 'reopen'"
|
|
475
479
|
@correct="correctResult = detail!.result; modal = 'correct'"
|
|
476
|
-
@new-inquiry="
|
|
480
|
+
@new-inquiry="goToInquiries"
|
|
477
481
|
/>
|
|
478
482
|
|
|
479
483
|
<div v-if="modal === 'register'" :class="shared.dialogOverlay" @mousedown="(e) => { if (e.target === e.currentTarget) modal = null; }">
|
|
@@ -19,6 +19,10 @@ const adjacent = computed(() => findAdjacent(props.log, props.all));
|
|
|
19
19
|
|
|
20
20
|
const asideRef = ref<HTMLElement | null>(null);
|
|
21
21
|
useOutsideClose(asideRef, () => emit('close'));
|
|
22
|
+
|
|
23
|
+
function goTo(path: string) {
|
|
24
|
+
window.location.href = path;
|
|
25
|
+
}
|
|
22
26
|
</script>
|
|
23
27
|
|
|
24
28
|
<template>
|
|
@@ -37,8 +41,8 @@ useOutsideClose(asideRef, () => emit('close'));
|
|
|
37
41
|
<button type="button" :class="drawer.closeBtn" @click="emit('close')">×</button>
|
|
38
42
|
</div>
|
|
39
43
|
<div v-if="log.relatedInquiryId || log.relatedConsultationId" :class="drawer.actionRow">
|
|
40
|
-
<button v-if="log.relatedInquiryId" type="button" :class="drawer.actionLink" @click="(
|
|
41
|
-
<button v-if="log.relatedConsultationId" type="button" :class="drawer.actionLink" @click="(
|
|
44
|
+
<button v-if="log.relatedInquiryId" type="button" :class="drawer.actionLink" @click="goTo('/cs/inquiries')">관련 문의 보기 ↗</button>
|
|
45
|
+
<button v-if="log.relatedConsultationId" type="button" :class="drawer.actionLink" @click="goTo('/cs/consultations')">관련 상담 보기 ↗</button>
|
|
42
46
|
</div>
|
|
43
47
|
</div>
|
|
44
48
|
|
|
@@ -25,6 +25,10 @@ function notify(message: string) {
|
|
|
25
25
|
showToast({ message, type: 'success' });
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
function goTo(path: string) {
|
|
29
|
+
window.location.href = path;
|
|
30
|
+
}
|
|
31
|
+
|
|
28
32
|
function update(patch: Partial<ProductInquiry>) {
|
|
29
33
|
Object.assign(props.inquiry, patch);
|
|
30
34
|
emit('change');
|
|
@@ -96,13 +100,13 @@ function toggleHidden() {
|
|
|
96
100
|
<div :class="drawer.sectionTitleLoose">상품 정보</div>
|
|
97
101
|
<div :class="drawer.linkedItem">
|
|
98
102
|
<div><strong>{{ productName(inquiry.productCode) }}</strong><div :class="drawer.sub">{{ inquiry.productCode }}</div></div>
|
|
99
|
-
<button type="button" :class="drawer.actionLink" @click="(
|
|
103
|
+
<button type="button" :class="drawer.actionLink" @click="goTo('/products')">상품 상세 보기</button>
|
|
100
104
|
</div>
|
|
101
105
|
|
|
102
106
|
<div :class="drawer.sectionTitleLoose">작성자 정보</div>
|
|
103
107
|
<div :class="drawer.linkedItem">
|
|
104
108
|
<strong>{{ inquiry.member }}</strong>
|
|
105
|
-
<button type="button" :class="drawer.actionLink" @click="(
|
|
109
|
+
<button type="button" :class="drawer.actionLink" @click="goTo('/members')">회원 상세 보기</button>
|
|
106
110
|
</div>
|
|
107
111
|
|
|
108
112
|
<div :class="drawer.sectionTitleLoose">문의 내용</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed,
|
|
2
|
+
import { computed, ref } from 'vue';
|
|
3
3
|
import { Button } from 'm2m-uiux-vue/Button';
|
|
4
4
|
import { ExcelDownloadButton, PageSizeSelect, showToast } from '../../components/common';
|
|
5
5
|
import DataGrid from '../../components/DataGrid/DataGrid.vue';
|
|
@@ -16,6 +16,10 @@ function setTab(next: string) {
|
|
|
16
16
|
router.replace({ query: { ...route.query, tab: next } });
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function isTab(t: 'safety' | 'config' | 'history') {
|
|
20
|
+
return tab.value === t;
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
const lowStockAlert = ref(true);
|
|
20
24
|
const soldOutAlert = ref(true);
|
|
21
25
|
const imminentAlert = ref(true);
|
|
@@ -44,9 +48,9 @@ function toggleChannel(ch: string) {
|
|
|
44
48
|
<template #header-tabs>
|
|
45
49
|
<div :class="styles.viewToggleRow" style="margin-top: 12px; margin-bottom: 4px">
|
|
46
50
|
<div :class="styles.viewToggle">
|
|
47
|
-
<button type="button" :class="[styles.viewToggleBtn,
|
|
48
|
-
<button type="button" :class="[styles.viewToggleBtn,
|
|
49
|
-
<button type="button" :class="[styles.viewToggleBtn,
|
|
51
|
+
<button type="button" :class="[styles.viewToggleBtn, isTab('safety') && styles.active]" @click="setTab('safety')">안전재고 기준</button>
|
|
52
|
+
<button type="button" :class="[styles.viewToggleBtn, isTab('config') && styles.active]" @click="setTab('config')">알림 설정</button>
|
|
53
|
+
<button type="button" :class="[styles.viewToggleBtn, isTab('history') && styles.active]" @click="setTab('history')">알림 내역</button>
|
|
50
54
|
</div>
|
|
51
55
|
</div>
|
|
52
56
|
</template>
|
|
@@ -60,9 +64,9 @@ function toggleChannel(ch: string) {
|
|
|
60
64
|
<template #header-tabs>
|
|
61
65
|
<div :class="styles.viewToggleRow" style="margin-top: 12px; margin-bottom: 4px">
|
|
62
66
|
<div :class="styles.viewToggle">
|
|
63
|
-
<button type="button" :class="[styles.viewToggleBtn,
|
|
64
|
-
<button type="button" :class="[styles.viewToggleBtn,
|
|
65
|
-
<button type="button" :class="[styles.viewToggleBtn,
|
|
67
|
+
<button type="button" :class="[styles.viewToggleBtn, isTab('safety') && styles.active]" @click="setTab('safety')">안전재고 기준</button>
|
|
68
|
+
<button type="button" :class="[styles.viewToggleBtn, isTab('config') && styles.active]" @click="setTab('config')">알림 설정</button>
|
|
69
|
+
<button type="button" :class="[styles.viewToggleBtn, isTab('history') && styles.active]" @click="setTab('history')">알림 내역</button>
|
|
66
70
|
</div>
|
|
67
71
|
</div>
|
|
68
72
|
</template>
|
|
@@ -78,9 +82,9 @@ function toggleChannel(ch: string) {
|
|
|
78
82
|
</div>
|
|
79
83
|
<div :class="styles.viewToggleRow" style="margin-top: 12px; margin-bottom: 4px">
|
|
80
84
|
<div :class="styles.viewToggle">
|
|
81
|
-
<button type="button" :class="[styles.viewToggleBtn,
|
|
82
|
-
<button type="button" :class="[styles.viewToggleBtn,
|
|
83
|
-
<button type="button" :class="[styles.viewToggleBtn,
|
|
85
|
+
<button type="button" :class="[styles.viewToggleBtn, isTab('safety') && styles.active]" @click="setTab('safety')">안전재고 기준</button>
|
|
86
|
+
<button type="button" :class="[styles.viewToggleBtn, isTab('config') && styles.active]" @click="setTab('config')">알림 설정</button>
|
|
87
|
+
<button type="button" :class="[styles.viewToggleBtn, isTab('history') && styles.active]" @click="setTab('history')">알림 내역</button>
|
|
84
88
|
</div>
|
|
85
89
|
</div>
|
|
86
90
|
<div :class="styles.definitionStrip">
|
|
@@ -13,6 +13,10 @@ const tab = computed(() => (route.query.tab === 'outbound' ? 'outbound' : 'inbou
|
|
|
13
13
|
function setTab(next: 'inbound' | 'outbound') {
|
|
14
14
|
router.replace({ query: { ...route.query, tab: next } });
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
function isTab(t: 'inbound' | 'outbound') {
|
|
18
|
+
return tab.value === t;
|
|
19
|
+
}
|
|
16
20
|
</script>
|
|
17
21
|
|
|
18
22
|
<template>
|
|
@@ -20,8 +24,8 @@ function setTab(next: 'inbound' | 'outbound') {
|
|
|
20
24
|
<template #header-tabs>
|
|
21
25
|
<div :class="styles.viewToggleRow" style="margin-top: 12px; margin-bottom: 4px">
|
|
22
26
|
<div :class="styles.viewToggle">
|
|
23
|
-
<button type="button" :class="[styles.viewToggleBtn,
|
|
24
|
-
<button type="button" :class="[styles.viewToggleBtn,
|
|
27
|
+
<button type="button" :class="[styles.viewToggleBtn, isTab('inbound') && styles.active]" @click="setTab('inbound')">입고 관리</button>
|
|
28
|
+
<button type="button" :class="[styles.viewToggleBtn, isTab('outbound') && styles.active]" @click="setTab('outbound')">출고 관리</button>
|
|
25
29
|
</div>
|
|
26
30
|
</div>
|
|
27
31
|
</template>
|
|
@@ -30,8 +34,8 @@ function setTab(next: 'inbound' | 'outbound') {
|
|
|
30
34
|
<template #header-tabs>
|
|
31
35
|
<div :class="styles.viewToggleRow" style="margin-top: 12px; margin-bottom: 4px">
|
|
32
36
|
<div :class="styles.viewToggle">
|
|
33
|
-
<button type="button" :class="[styles.viewToggleBtn,
|
|
34
|
-
<button type="button" :class="[styles.viewToggleBtn,
|
|
37
|
+
<button type="button" :class="[styles.viewToggleBtn, isTab('inbound') && styles.active]" @click="setTab('inbound')">입고 관리</button>
|
|
38
|
+
<button type="button" :class="[styles.viewToggleBtn, isTab('outbound') && styles.active]" @click="setTab('outbound')">출고 관리</button>
|
|
35
39
|
</div>
|
|
36
40
|
</div>
|
|
37
41
|
</template>
|