feat(rulebook): RulesView three-pane shell + child panes + rule editor
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted } from "vue";
|
||||
import { useRulebooksStore } from "@/stores/rulebooks";
|
||||
|
||||
const props = defineProps<{ ruleId: number | null; topicId: number | null }>();
|
||||
const emit = defineEmits<{ close: [] }>();
|
||||
|
||||
const store = useRulebooksStore();
|
||||
const title = ref("");
|
||||
const statement = ref("");
|
||||
const why = ref("");
|
||||
const howToApply = ref("");
|
||||
|
||||
const isCreating = ref(props.ruleId === null);
|
||||
|
||||
async function load() {
|
||||
if (props.ruleId !== null) {
|
||||
await store.fetchRule(props.ruleId);
|
||||
const r = store.currentRule;
|
||||
if (r) {
|
||||
title.value = r.title;
|
||||
statement.value = r.statement;
|
||||
why.value = r.why || "";
|
||||
howToApply.value = r.how_to_apply || "";
|
||||
}
|
||||
} else {
|
||||
title.value = "";
|
||||
statement.value = "";
|
||||
why.value = "";
|
||||
howToApply.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
async function save() {
|
||||
if (!title.value.trim() || !statement.value.trim()) {
|
||||
emit("close");
|
||||
return;
|
||||
}
|
||||
if (isCreating.value && props.topicId !== null) {
|
||||
await store.createRule(props.topicId, {
|
||||
title: title.value, statement: statement.value,
|
||||
why: why.value, how_to_apply: howToApply.value,
|
||||
});
|
||||
} else if (props.ruleId !== null) {
|
||||
await store.updateRule(props.ruleId, {
|
||||
title: title.value, statement: statement.value,
|
||||
why: why.value, how_to_apply: howToApply.value,
|
||||
});
|
||||
}
|
||||
emit("close");
|
||||
}
|
||||
|
||||
async function remove() {
|
||||
if (props.ruleId === null) return;
|
||||
if (!confirm("Delete this rule? This cannot be undone.")) return;
|
||||
await store.deleteRule(props.ruleId);
|
||||
emit("close");
|
||||
}
|
||||
|
||||
onMounted(load);
|
||||
watch(() => props.ruleId, load);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="backdrop" @click="save">
|
||||
<aside class="slide-over" @click.stop>
|
||||
<header>
|
||||
<h2>{{ isCreating ? "New rule" : "Edit rule" }}</h2>
|
||||
<button v-if="!isCreating" class="trash" @click="remove" aria-label="Delete">🗑</button>
|
||||
<button class="close" @click="save" aria-label="Close">×</button>
|
||||
</header>
|
||||
<label>
|
||||
Title
|
||||
<input v-model="title" placeholder="e.g. dev is home" />
|
||||
</label>
|
||||
<label>
|
||||
Statement <span class="required">*</span>
|
||||
<textarea v-model="statement" rows="3" placeholder="The actionable instruction (1-2 sentences)." />
|
||||
</label>
|
||||
<label>
|
||||
Why
|
||||
<textarea v-model="why" rows="4" placeholder="Rationale — the reason this rule exists." />
|
||||
</label>
|
||||
<label>
|
||||
How to apply
|
||||
<textarea v-model="howToApply" rows="4" placeholder="When / where this kicks in." />
|
||||
</label>
|
||||
</aside>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.backdrop {
|
||||
position: fixed; inset: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 100;
|
||||
}
|
||||
.slide-over {
|
||||
position: fixed; top: 0; right: 0; bottom: 0;
|
||||
width: min(520px, 90vw);
|
||||
background: var(--color-surface, #18181b);
|
||||
border-left: 2px solid var(--color-primary, #6366f1);
|
||||
padding: 1.5rem;
|
||||
overflow-y: auto;
|
||||
box-shadow: -8px 0 32px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
header { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 1rem; }
|
||||
header h2 {
|
||||
flex: 1; margin: 0;
|
||||
font-family: Fraunces, serif; font-style: italic;
|
||||
}
|
||||
label { display: block; margin-bottom: 1rem; }
|
||||
.required { color: var(--color-primary, #6366f1); }
|
||||
input, textarea {
|
||||
width: 100%; margin-top: 0.25rem;
|
||||
background: var(--color-bg, #111113); color: inherit;
|
||||
border: 1px solid var(--color-border, #2a2a2e); border-radius: 6px;
|
||||
padding: 0.5rem; font: inherit;
|
||||
font-family: inherit;
|
||||
}
|
||||
.trash, .close { background: none; border: none; cursor: pointer; opacity: 0.6; font-size: 1.25em; }
|
||||
.trash:hover, .close:hover { opacity: 1; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user