feat(rulebook): RulesView three-pane shell + child panes + rule editor
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useRulebooksStore } from "@/stores/rulebooks";
|
||||
import RulebookListPane from "@/components/rules/RulebookListPane.vue";
|
||||
import RulebookDetailPane from "@/components/rules/RulebookDetailPane.vue";
|
||||
import RuleListPane from "@/components/rules/RuleListPane.vue";
|
||||
import RuleEditorSlideOver from "@/components/rules/RuleEditorSlideOver.vue";
|
||||
|
||||
const store = useRulebooksStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const selectedRulebookId = ref<number | null>(null);
|
||||
const selectedTopicId = ref<number | null>(null);
|
||||
const editingRuleId = ref<number | null>(null);
|
||||
const creatingRuleForTopic = ref<number | null>(null);
|
||||
|
||||
function syncFromRoute() {
|
||||
const rb = route.query.rb ? Number(route.query.rb) : null;
|
||||
const topic = route.query.topic ? Number(route.query.topic) : null;
|
||||
const rule = route.query.rule ? Number(route.query.rule) : null;
|
||||
selectedRulebookId.value = rb;
|
||||
selectedTopicId.value = topic;
|
||||
editingRuleId.value = rule;
|
||||
}
|
||||
|
||||
function selectRulebook(id: number) {
|
||||
selectedRulebookId.value = id;
|
||||
selectedTopicId.value = null;
|
||||
router.replace({ query: { rb: String(id) } });
|
||||
store.fetchTopics(id);
|
||||
}
|
||||
|
||||
function selectTopic(id: number) {
|
||||
selectedTopicId.value = id;
|
||||
router.replace({ query: { ...route.query, topic: String(id) } });
|
||||
store.fetchRules(id);
|
||||
}
|
||||
|
||||
function openRule(id: number) {
|
||||
editingRuleId.value = id;
|
||||
router.replace({ query: { ...route.query, rule: String(id) } });
|
||||
}
|
||||
|
||||
function closeRuleEditor() {
|
||||
editingRuleId.value = null;
|
||||
creatingRuleForTopic.value = null;
|
||||
const { rule, ...rest } = route.query;
|
||||
void rule;
|
||||
router.replace({ query: rest });
|
||||
}
|
||||
|
||||
function startCreatingRule(topicId: number) {
|
||||
creatingRuleForTopic.value = topicId;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await store.fetchRulebooks();
|
||||
syncFromRoute();
|
||||
if (selectedRulebookId.value) await store.fetchTopics(selectedRulebookId.value);
|
||||
if (selectedTopicId.value) await store.fetchRules(selectedTopicId.value);
|
||||
});
|
||||
|
||||
watch(() => route.query, syncFromRoute);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rules-view">
|
||||
<RulebookListPane
|
||||
:rulebooks="store.rulebooks"
|
||||
:selected-id="selectedRulebookId"
|
||||
@select="selectRulebook"
|
||||
/>
|
||||
<RulebookDetailPane
|
||||
v-if="selectedRulebookId !== null"
|
||||
:rulebook-id="selectedRulebookId"
|
||||
:topics="store.topicsByRulebook[selectedRulebookId] || []"
|
||||
:selected-topic-id="selectedTopicId"
|
||||
@select-topic="selectTopic"
|
||||
/>
|
||||
<div v-else class="pane empty">
|
||||
<p>Select a rulebook to view its topics.</p>
|
||||
</div>
|
||||
<RuleListPane
|
||||
v-if="selectedTopicId !== null"
|
||||
:topic-id="selectedTopicId"
|
||||
:rules="store.rulesByTopic[selectedTopicId] || []"
|
||||
@open-rule="openRule"
|
||||
@create-rule="startCreatingRule"
|
||||
/>
|
||||
<div v-else class="pane empty">
|
||||
<p>Select a topic to view its rules.</p>
|
||||
</div>
|
||||
<RuleEditorSlideOver
|
||||
v-if="editingRuleId !== null || creatingRuleForTopic !== null"
|
||||
:rule-id="editingRuleId"
|
||||
:topic-id="creatingRuleForTopic"
|
||||
@close="closeRuleEditor"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.rules-view {
|
||||
display: grid;
|
||||
grid-template-columns: 280px 1fr 1fr;
|
||||
height: 100vh;
|
||||
gap: 1px;
|
||||
background: var(--color-border, #2a2a2e);
|
||||
}
|
||||
.pane.empty {
|
||||
background: var(--color-surface, #18181b);
|
||||
padding: 1rem;
|
||||
opacity: 0.6;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user