1. PC 네이티브 confirm 제목('sb_pt') 노출 → '고정지출 등록' 인앱 확인 모달로 교체
(성공 시 토스트 안내). electron app.setName('Slim Budget')로 다른 네이티브
대화상자 제목도 개선(다음 Electron 빌드 시 반영).
2. 분류 관리 드래그를 계층형으로: 대분류는 블록 단위(소분류 함께) 이동,
소분류는 그 대분류 안에서만 이동(고유 group). 로컬 즉시반영으로 스냅백 방지.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,9 @@ const { app, BrowserWindow, shell, session } = require('electron')
|
|||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
|
// 네이티브 대화상자(alert/confirm) 제목은 app.getName() 을 쓴다 → package.json name('sb_pt') 대신 표기.
|
||||||
|
app.setName('Slim Budget')
|
||||||
|
|
||||||
const APP_URL = 'https://app.sblog.kr'
|
const APP_URL = 'https://app.sblog.kr'
|
||||||
|
|
||||||
// 창 크기/위치를 userData 에 저장해 다음 실행 때 복원 (기본 해상도 고정 대신 사용자가 맞춘 크기 유지)
|
// 창 크기/위치를 userData 에 저장해 다음 실행 때 복원 (기본 해상도 고정 대신 사용자가 맞춘 크기 유지)
|
||||||
|
|||||||
@@ -579,8 +579,18 @@ async function remove(e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 현재 수정 중인 내역을 '매월' 고정지출로 바로 등록 (주기·시작일은 고정지출 화면에서 변경)
|
// 고정지출 등록 확인 모달 상태 (네이티브 confirm 대신 — PC 팝업 제목 'sb_pt' 노출 방지)
|
||||||
async function registerAsRecurring() {
|
const recurConfirm = reactive({ open: false, title: '', dom: 1, amount: 0, payload: null })
|
||||||
|
const flashMsg = ref('')
|
||||||
|
let flashTimer = null
|
||||||
|
function flash(msg) {
|
||||||
|
flashMsg.value = msg
|
||||||
|
if (flashTimer) clearTimeout(flashTimer)
|
||||||
|
flashTimer = setTimeout(() => (flashMsg.value = ''), 2800)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 현재 수정 중인 내역을 '매월' 고정지출로 등록 — 확인 모달을 띄운다
|
||||||
|
function registerAsRecurring() {
|
||||||
if (form.type === 'REPAYMENT') return // 고정지출은 수입/지출/이체만
|
if (form.type === 'REPAYMENT') return // 고정지출은 수입/지출/이체만
|
||||||
const amount = Number(form.amount)
|
const amount = Number(form.amount)
|
||||||
if (!amount || amount <= 0) {
|
if (!amount || amount <= 0) {
|
||||||
@@ -597,10 +607,10 @@ async function registerAsRecurring() {
|
|||||||
// 이번 회차 중복 방지: 시작일을 내역 다음날로 → 다음 발생부터 생성
|
// 이번 회차 중복 방지: 시작일을 내역 다음날로 → 다음 발생부터 생성
|
||||||
const start = new Date(y, m - 1, d + 1)
|
const start = new Date(y, m - 1, d + 1)
|
||||||
const startStr = `${start.getFullYear()}-${String(start.getMonth() + 1).padStart(2, '0')}-${String(start.getDate()).padStart(2, '0')}`
|
const startStr = `${start.getFullYear()}-${String(start.getMonth() + 1).padStart(2, '0')}-${String(start.getDate()).padStart(2, '0')}`
|
||||||
if (!confirm(`'${title}'\n매월 ${dom}일 · ${won(amount)} 고정지출로 등록할까요?\n(주기·시작일은 고정지출 화면에서 변경할 수 있어요)`)) return
|
recurConfirm.title = title
|
||||||
submitting.value = true
|
recurConfirm.dom = dom
|
||||||
try {
|
recurConfirm.amount = amount
|
||||||
await accountApi.createRecurring({
|
recurConfirm.payload = {
|
||||||
title,
|
title,
|
||||||
type: form.type,
|
type: form.type,
|
||||||
amount,
|
amount,
|
||||||
@@ -612,11 +622,21 @@ async function registerAsRecurring() {
|
|||||||
dayOfMonth: dom,
|
dayOfMonth: dom,
|
||||||
startDate: startStr,
|
startDate: startStr,
|
||||||
active: true,
|
active: true,
|
||||||
})
|
}
|
||||||
|
recurConfirm.open = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doRegisterRecurring() {
|
||||||
|
if (!recurConfirm.payload) return
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
await accountApi.createRecurring(recurConfirm.payload)
|
||||||
|
recurConfirm.open = false
|
||||||
formOpen.value = false
|
formOpen.value = false
|
||||||
alert('고정지출로 등록했습니다. 고정지출 화면에서 주기를 변경할 수 있어요.')
|
flash('고정지출로 등록했습니다. 고정지출 화면에서 주기를 변경할 수 있어요.')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
formError.value = e.response?.data?.message || '고정지출 등록에 실패했습니다.'
|
formError.value = e.response?.data?.message || '고정지출 등록에 실패했습니다.'
|
||||||
|
recurConfirm.open = false
|
||||||
} finally {
|
} finally {
|
||||||
submitting.value = false
|
submitting.value = false
|
||||||
}
|
}
|
||||||
@@ -644,6 +664,7 @@ onMounted(async () => {
|
|||||||
<h1>가계부<span v-if="pendingCount" class="pending-count">확인 필요 {{ pendingCount }}건</span></h1>
|
<h1>가계부<span v-if="pendingCount" class="pending-count">확인 필요 {{ pendingCount }}건</span></h1>
|
||||||
<IconBtn icon="plus" title="내역 추가" variant="primary" @click="openCreate" />
|
<IconBtn icon="plus" title="내역 추가" variant="primary" @click="openCreate" />
|
||||||
</header>
|
</header>
|
||||||
|
<Transition name="fade"><p v-if="flashMsg" class="flash">{{ flashMsg }}</p></Transition>
|
||||||
|
|
||||||
<!-- 카드 결제 알림 자동인식 권한 안내 (네이티브, 미허용 시) -->
|
<!-- 카드 결제 알림 자동인식 권한 안내 (네이티브, 미허용 시) -->
|
||||||
<div v-if="notifNative && !notifEnabled" class="notif-banner">
|
<div v-if="notifNative && !notifEnabled" class="notif-banner">
|
||||||
@@ -936,6 +957,26 @@ onMounted(async () => {
|
|||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
|
|
||||||
|
<!-- 고정지출 등록 확인 (네이티브 confirm 대체 — 제목 'sb_pt' 노출 방지) -->
|
||||||
|
<Teleport to="body">
|
||||||
|
<Transition name="fade">
|
||||||
|
<div v-if="recurConfirm.open" class="modal-backdrop" @click.self="recurConfirm.open = false">
|
||||||
|
<div class="modal confirm-modal" role="dialog" aria-modal="true">
|
||||||
|
<h2>고정지출 등록</h2>
|
||||||
|
<p class="confirm-body">
|
||||||
|
<b>{{ recurConfirm.title }}</b><br />
|
||||||
|
매월 <b>{{ recurConfirm.dom }}일</b> · <b>{{ won(recurConfirm.amount) }}</b> 으로 등록할까요?
|
||||||
|
</p>
|
||||||
|
<p class="confirm-sub">주기·시작일은 고정지출 화면에서 변경할 수 있어요.</p>
|
||||||
|
<div class="buttons">
|
||||||
|
<IconBtn icon="close" title="취소" @click="recurConfirm.open = false" />
|
||||||
|
<IconBtn icon="save" title="등록" variant="primary" :disabled="submitting" @click="doRegisterRecurring" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</Teleport>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -1256,6 +1297,26 @@ button.primary {
|
|||||||
background: rgba(0, 0, 0, 0.5);
|
background: rgba(0, 0, 0, 0.5);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
.flash {
|
||||||
|
margin: 0.5rem 0 0;
|
||||||
|
padding: 0.6rem 0.9rem;
|
||||||
|
background: hsla(160, 100%, 37%, 0.12);
|
||||||
|
border: 1px solid hsla(160, 100%, 37%, 0.5);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.confirm-modal {
|
||||||
|
max-width: 340px;
|
||||||
|
}
|
||||||
|
.confirm-body {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.confirm-sub {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
.modal {
|
.modal {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ const majors = computed(() => rows.value.filter((c) => c.parentId == null))
|
|||||||
function parentOptions(c) {
|
function parentOptions(c) {
|
||||||
return majors.value.filter((m) => m.id !== c.id)
|
return majors.value.filter((m) => m.id !== c.id)
|
||||||
}
|
}
|
||||||
|
// 대분류 → 소분류 트리 (major/children 모두 실제 row 참조 유지 → v-model 바인딩 가능)
|
||||||
|
const tree = computed(() =>
|
||||||
|
majors.value.map((m) => ({ major: m, children: rows.value.filter((c) => c.parentId === m.id) })),
|
||||||
|
)
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -39,36 +43,66 @@ async function load() {
|
|||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function reload() {
|
||||||
|
await load()
|
||||||
|
await nextTick()
|
||||||
|
initSortable()
|
||||||
|
}
|
||||||
|
|
||||||
// ===== 드래그앤드랍 정렬 (SortableJS, 터치 지원) =====
|
// ===== 계층 드래그 정렬 (대분류 블록 / 대분류 내 소분류) =====
|
||||||
const listEl = ref(null)
|
const majorListEl = ref(null)
|
||||||
let sortable = null
|
let sortables = []
|
||||||
|
function destroySortables() {
|
||||||
|
sortables.forEach((s) => s.destroy())
|
||||||
|
sortables = []
|
||||||
|
}
|
||||||
function initSortable() {
|
function initSortable() {
|
||||||
if (sortable) {
|
destroySortables()
|
||||||
sortable.destroy()
|
if (!majorListEl.value) return
|
||||||
sortable = null
|
// 대분류 순서 — 블록 단위라 소분류가 함께 이동
|
||||||
}
|
sortables.push(
|
||||||
if (!listEl.value) return
|
Sortable.create(majorListEl.value, {
|
||||||
sortable = Sortable.create(listEl.value, {
|
handle: '.major-handle',
|
||||||
handle: '.drag-handle',
|
draggable: '.major-block',
|
||||||
animation: 150,
|
animation: 150,
|
||||||
onEnd: (evt) => {
|
onEnd: () => {
|
||||||
if (evt.oldIndex === evt.newIndex) return
|
const ids = [...majorListEl.value.querySelectorAll(':scope > .major-block')].map((li) => Number(li.dataset.id))
|
||||||
const arr = rows.value
|
persistOrder(ids)
|
||||||
const moved = arr.splice(evt.oldIndex, 1)[0]
|
|
||||||
arr.splice(evt.newIndex, 0, moved)
|
|
||||||
persistOrder(arr.map((r) => r.id))
|
|
||||||
},
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
// 각 대분류의 소분류 순서 — 고유 group 으로 다른 대분류로는 이동 불가
|
||||||
|
majorListEl.value.querySelectorAll('.sub-list').forEach((ul) => {
|
||||||
|
sortables.push(
|
||||||
|
Sortable.create(ul, {
|
||||||
|
handle: '.sub-handle',
|
||||||
|
draggable: '.sub-item',
|
||||||
|
group: 'sub-' + ul.dataset.parent,
|
||||||
|
animation: 150,
|
||||||
|
onEnd: () => {
|
||||||
|
const ids = [...ul.querySelectorAll(':scope > .sub-item')].map((li) => Number(li.dataset.id))
|
||||||
|
if (ids.length) persistOrder(ids)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// 로컬 순서 즉시 반영(스냅백 방지) 후 백엔드 저장
|
||||||
|
function reorderLocal(ids) {
|
||||||
|
const idSet = new Set(ids)
|
||||||
|
const moved = ids.map((id) => categories.value.find((c) => c.id === id)).filter(Boolean)
|
||||||
|
let k = 0
|
||||||
|
categories.value = categories.value.map((c) => (idSet.has(c.id) ? moved[k++] : c))
|
||||||
|
syncRows()
|
||||||
|
}
|
||||||
async function persistOrder(ids) {
|
async function persistOrder(ids) {
|
||||||
|
reorderLocal(ids)
|
||||||
try {
|
try {
|
||||||
await accountApi.reorderCategories(activeType.value, ids)
|
await accountApi.reorderCategories(activeType.value, ids)
|
||||||
await load()
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(e.response?.data?.message || '순서 저장에 실패했습니다.')
|
alert(e.response?.data?.message || '순서 저장에 실패했습니다.')
|
||||||
await load()
|
|
||||||
}
|
}
|
||||||
|
await reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addCategory() {
|
async function addCategory() {
|
||||||
@@ -77,7 +111,7 @@ async function addCategory() {
|
|||||||
try {
|
try {
|
||||||
await accountApi.createCategory({ type: activeType.value, name, parentId: newParent.value || null })
|
await accountApi.createCategory({ type: activeType.value, name, parentId: newParent.value || null })
|
||||||
newName.value = ''
|
newName.value = ''
|
||||||
await load()
|
await reload()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(e.response?.data?.message || '추가에 실패했습니다.')
|
alert(e.response?.data?.message || '추가에 실패했습니다.')
|
||||||
}
|
}
|
||||||
@@ -89,7 +123,7 @@ async function saveCategory(c) {
|
|||||||
try {
|
try {
|
||||||
// parentId 는 항상 현재값을 함께 전송(미전송 시 대분류로 풀림)
|
// parentId 는 항상 현재값을 함께 전송(미전송 시 대분류로 풀림)
|
||||||
await accountApi.updateCategory(c.id, { type: c.type, name, parentId: c.parentId || null })
|
await accountApi.updateCategory(c.id, { type: c.type, name, parentId: c.parentId || null })
|
||||||
await load()
|
await reload()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(e.response?.data?.message || '수정에 실패했습니다.')
|
alert(e.response?.data?.message || '수정에 실패했습니다.')
|
||||||
}
|
}
|
||||||
@@ -99,7 +133,7 @@ async function removeCategory(c) {
|
|||||||
if (!confirm(`'${c.name}' 분류를 삭제할까요? (기존 내역의 분류명은 그대로 유지됩니다)`)) return
|
if (!confirm(`'${c.name}' 분류를 삭제할까요? (기존 내역의 분류명은 그대로 유지됩니다)`)) return
|
||||||
try {
|
try {
|
||||||
await accountApi.removeCategory(c.id)
|
await accountApi.removeCategory(c.id)
|
||||||
await load()
|
await reload()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(e.response?.data?.message || '삭제에 실패했습니다.')
|
alert(e.response?.data?.message || '삭제에 실패했습니다.')
|
||||||
}
|
}
|
||||||
@@ -109,17 +143,23 @@ async function importExisting() {
|
|||||||
if (!confirm('기존 내역에서 사용한 분류들을 목록으로 가져올까요?')) return
|
if (!confirm('기존 내역에서 사용한 분류들을 목록으로 가져올까요?')) return
|
||||||
try {
|
try {
|
||||||
categories.value = await accountApi.importCategories()
|
categories.value = await accountApi.importCategories()
|
||||||
|
await nextTick()
|
||||||
|
initSortable()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(e.response?.data?.message || '불러오기에 실패했습니다.')
|
alert(e.response?.data?.message || '불러오기에 실패했습니다.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
// 탭(수입/지출) 전환 시 재렌더 → 드래그 재초기화
|
||||||
await load()
|
watch(activeType, async () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
initSortable()
|
initSortable()
|
||||||
})
|
})
|
||||||
onBeforeUnmount(() => sortable?.destroy())
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await reload()
|
||||||
|
})
|
||||||
|
onBeforeUnmount(destroySortables)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -148,30 +188,50 @@ onBeforeUnmount(() => sortable?.destroy())
|
|||||||
<IconBtn icon="plus" title="추가" variant="primary" type="submit" size="sm" />
|
<IconBtn icon="plus" title="추가" variant="primary" type="submit" size="sm" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<p class="hint sm">≡ 손잡이를 끌어 순서 변경. 각 행의 대분류를 바꾸면 소분류로 묶입니다.</p>
|
<p class="hint sm">≡ 손잡이로 순서 변경 — <b>대분류</b>는 소분류와 함께, <b>소분류</b>는 그 대분류 안에서만 이동합니다. 다른 대분류로 옮기려면 각 행의 ‘대분류’를 바꾸세요.</p>
|
||||||
|
|
||||||
<p v-if="error" class="msg error">{{ error }}</p>
|
<p v-if="error" class="msg error">{{ error }}</p>
|
||||||
<p v-if="loading" class="msg">불러오는 중...</p>
|
<p v-if="loading" class="msg">불러오는 중...</p>
|
||||||
|
|
||||||
<ul v-show="!loading && rows.length" ref="listEl" class="cat-list">
|
<ul v-show="!loading && tree.length" ref="majorListEl" class="major-list">
|
||||||
<li v-for="c in rows" :key="c.id" :data-id="c.id" class="cat-row" :class="{ child: c.parentId != null }">
|
<li v-for="g in tree" :key="g.major.id" :data-id="g.major.id" class="major-block">
|
||||||
|
<div class="cat-row major-row">
|
||||||
<div class="cat-main">
|
<div class="cat-main">
|
||||||
<span class="drag-handle" title="드래그하여 순서 변경">≡</span>
|
<span class="drag-handle major-handle" title="대분류 순서 변경(소분류 함께 이동)">≡</span>
|
||||||
<span v-if="c.parentId != null" class="sub-mark" title="소분류">↳</span>
|
<span class="major-badge">대</span>
|
||||||
<input v-model="c.name" class="cat-name" @keyup.enter="saveCategory(c)" />
|
<input v-model="g.major.name" class="cat-name" @keyup.enter="saveCategory(g.major)" />
|
||||||
<IconBtn icon="check" title="저장" size="sm" @click="saveCategory(c)" />
|
<IconBtn icon="check" title="저장" size="sm" @click="saveCategory(g.major)" />
|
||||||
<IconBtn icon="trash" title="삭제" variant="danger" size="sm" @click="removeCategory(c)" />
|
<IconBtn icon="trash" title="삭제" variant="danger" size="sm" @click="removeCategory(g.major)" />
|
||||||
</div>
|
</div>
|
||||||
<label class="cat-parent">
|
<label class="cat-parent">
|
||||||
<span class="pl">대분류</span>
|
<span class="pl">대분류</span>
|
||||||
<select v-model="c.parentId" class="parent-sel" title="대분류" @change="saveCategory(c)">
|
<select v-model="g.major.parentId" class="parent-sel" @change="saveCategory(g.major)">
|
||||||
<option :value="null">(없음 · 대분류)</option>
|
<option :value="null">(없음 · 대분류)</option>
|
||||||
<option v-for="m in parentOptions(c)" :key="m.id" :value="m.id">{{ m.name }}</option>
|
<option v-for="m in parentOptions(g.major)" :key="m.id" :value="m.id">{{ m.name }}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<ul class="sub-list" :data-parent="g.major.id">
|
||||||
|
<li v-for="s in g.children" :key="s.id" :data-id="s.id" class="sub-item cat-row child">
|
||||||
|
<div class="cat-main">
|
||||||
|
<span class="drag-handle sub-handle" title="소분류 순서 변경">≡</span>
|
||||||
|
<span class="sub-mark">↳</span>
|
||||||
|
<input v-model="s.name" class="cat-name" @keyup.enter="saveCategory(s)" />
|
||||||
|
<IconBtn icon="check" title="저장" size="sm" @click="saveCategory(s)" />
|
||||||
|
<IconBtn icon="trash" title="삭제" variant="danger" size="sm" @click="removeCategory(s)" />
|
||||||
|
</div>
|
||||||
|
<label class="cat-parent">
|
||||||
|
<span class="pl">대분류</span>
|
||||||
|
<select v-model="s.parentId" class="parent-sel" @change="saveCategory(s)">
|
||||||
|
<option v-for="m in parentOptions(s)" :key="m.id" :value="m.id">{{ m.name }}</option>
|
||||||
|
<option :value="null">(대분류로 분리)</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p v-if="!loading && !rows.length" class="msg">
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p v-if="!loading && !tree.length" class="msg">
|
||||||
등록된 {{ activeType === 'EXPENSE' ? '지출' : '수입' }} 분류가 없습니다.
|
등록된 {{ activeType === 'EXPENSE' ? '지출' : '수입' }} 분류가 없습니다.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
@@ -250,16 +310,43 @@ button.danger {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
.cat-list {
|
.major-list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
.major-block {
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.major-row {
|
||||||
|
background: var(--color-background-soft);
|
||||||
|
border-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
.major-badge {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
font-size: 0.62rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: hsla(160, 100%, 37%, 1);
|
||||||
|
border: 1px solid hsla(160, 100%, 37%, 0.6);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.05rem 0.25rem;
|
||||||
|
}
|
||||||
|
.sub-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0 0 0 0.4rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.sub-item:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
.cat-row {
|
.cat-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.35rem;
|
gap: 0.35rem;
|
||||||
padding: 0.5rem 0;
|
padding: 0.5rem 0.5rem;
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
background: var(--color-background);
|
background: var(--color-background);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user