3b435dc0ba
The Cleanup + Maintenance sections had ~17 full-width stacked cards with long descriptions — a hunt to scan. Operator wants compact, sectioned, scannable tiles (2026-06-18: keep both tabs, group inside, compact tiles in a grid). New common/MaintenanceTile.vue: a compact expandable tile (icon + short title + one-line blurb collapsed; click the header to expand the full controls/preview/ result inline; keyboard-accessible button + focus ring; tints the icon, keeps a running task expanded). Cleanup tab (this pass) restructured into 3 sections — Import-filter audits (Min dimensions, Transparency, Single-color) / Duplicates & posts (Bare posts, Duplicate posts, Deduplicate videos, Gated-post previews) / Tags (Unused, Legacy, Reset content tagging, Standardize casing) — each a responsive grid of tiles. PostMaintenanceCard split into 2 tiles, TagMaintenanceCard into 4. Moved VideoDedupCard + GatedPurgeCard from the Maintenance tab here (both are destructive content cleanup). All card logic unchanged — only the chrome. Maintenance tab tiling is pass 2 (TODO noted in MaintenancePanel). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
122 lines
3.1 KiB
Vue
122 lines
3.1 KiB
Vue
<!--
|
|
Compact, expandable maintenance/cleanup action tile. Collapsed it shows just an
|
|
icon + short title + one-line purpose, so a section of these tiles in a grid is
|
|
scannable at a glance; clicking the header expands the full controls / preview /
|
|
result UI inline (the operator opted for "compact tiles in a grid, detail on
|
|
expand", 2026-06-18, replacing the old long stack of full-width cards).
|
|
|
|
Usage: wrap a card's action body in the default slot; pass icon/title/blurb.
|
|
`destructive` tints the icon error-red for delete actions. `open` can be forced
|
|
(e.g. keep a running task's tile expanded). Keyboard accessible: the header is a
|
|
real <button> with aria-expanded + focus ring.
|
|
-->
|
|
<template>
|
|
<v-card class="fc-tile" :class="{ 'fc-tile--open': isOpen }">
|
|
<button
|
|
type="button"
|
|
class="fc-tile__head"
|
|
:aria-expanded="isOpen"
|
|
@click="toggle"
|
|
>
|
|
<v-icon
|
|
:icon="icon"
|
|
:color="destructive ? 'error' : 'accent'"
|
|
class="fc-tile__icon"
|
|
/>
|
|
<span class="fc-tile__text">
|
|
<span class="fc-tile__title">{{ title }}</span>
|
|
<span class="fc-tile__blurb">{{ blurb }}</span>
|
|
</span>
|
|
<v-icon
|
|
:icon="isOpen ? 'mdi-chevron-up' : 'mdi-chevron-down'"
|
|
class="fc-tile__chev"
|
|
/>
|
|
</button>
|
|
<v-expand-transition>
|
|
<div v-show="isOpen" class="fc-tile__body">
|
|
<slot />
|
|
</div>
|
|
</v-expand-transition>
|
|
</v-card>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, ref, watch } from 'vue'
|
|
|
|
const props = defineProps({
|
|
icon: { type: String, required: true },
|
|
title: { type: String, required: true },
|
|
blurb: { type: String, default: '' },
|
|
destructive: { type: Boolean, default: false },
|
|
// Force-open (e.g. a tile whose task is mid-run). When set, the operator can
|
|
// still collapse it locally, but a change to `open` re-applies.
|
|
open: { type: Boolean, default: false },
|
|
})
|
|
|
|
const local = ref(props.open)
|
|
watch(() => props.open, (v) => { local.value = v })
|
|
const isOpen = computed(() => local.value)
|
|
|
|
function toggle() {
|
|
local.value = !local.value
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.fc-tile {
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
}
|
|
.fc-tile__head {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
width: 100%;
|
|
padding: 12px 14px;
|
|
background: transparent;
|
|
border: none;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
color: inherit;
|
|
}
|
|
.fc-tile__head:hover {
|
|
background: rgba(var(--v-theme-on-surface), 0.04);
|
|
}
|
|
.fc-tile__head:focus-visible {
|
|
outline: 2px solid rgb(var(--v-theme-accent));
|
|
outline-offset: -2px;
|
|
}
|
|
.fc-tile__icon {
|
|
flex: 0 0 auto;
|
|
}
|
|
.fc-tile__text {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
flex: 1 1 auto;
|
|
}
|
|
.fc-tile__title {
|
|
font-weight: 600;
|
|
font-size: 0.95rem;
|
|
line-height: 1.2;
|
|
}
|
|
.fc-tile__blurb {
|
|
font-size: 0.8rem;
|
|
line-height: 1.25;
|
|
color: rgb(var(--v-theme-on-surface-variant));
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.fc-tile--open .fc-tile__blurb {
|
|
white-space: normal;
|
|
}
|
|
.fc-tile__chev {
|
|
flex: 0 0 auto;
|
|
color: rgb(var(--v-theme-on-surface-variant));
|
|
}
|
|
.fc-tile__body {
|
|
padding: 0 14px 14px;
|
|
}
|
|
</style>
|