feat(plan): plan-task viewer shows Applicable Rules panel
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import { getProjectApplicableRules } from "@/api/rulebooks";
|
||||
import type { ApplicableRules } from "@/api/rulebooks";
|
||||
|
||||
const props = defineProps<{ projectId: number }>();
|
||||
const data = ref<ApplicableRules | null>(null);
|
||||
|
||||
function grouped(rules: ApplicableRules["rules"]) {
|
||||
const g: Record<string, Record<string, ApplicableRules["rules"]>> = {};
|
||||
for (const r of rules) {
|
||||
(g[r.rulebook_title] ??= {});
|
||||
(g[r.rulebook_title][r.topic_title] ??= []).push(r);
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
async function load() {
|
||||
data.value = await getProjectApplicableRules(props.projectId);
|
||||
}
|
||||
|
||||
onMounted(load);
|
||||
watch(() => props.projectId, load);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="plan-rules" v-if="data && data.rules.length">
|
||||
<h3>Applicable rules</h3>
|
||||
<div v-for="(topics, rb) in grouped(data.rules)" :key="rb" class="rb">
|
||||
<h4>{{ rb }}</h4>
|
||||
<div v-for="(rules, topic) in topics" :key="topic">
|
||||
<h5>{{ topic }}</h5>
|
||||
<ul>
|
||||
<li v-for="r in rules" :key="r.id">
|
||||
<strong>{{ r.title }}</strong> — {{ r.statement }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="data.truncated" class="truncated">
|
||||
Truncated at 50 rules — more apply.
|
||||
</p>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.plan-rules {
|
||||
margin-top: 1.5rem;
|
||||
border-top: 1px solid var(--color-border, #2a2a2e);
|
||||
padding-top: 1rem;
|
||||
}
|
||||
.plan-rules h3 {
|
||||
font-size: 0.9em; opacity: 0.7;
|
||||
text-transform: uppercase; letter-spacing: 0.05em;
|
||||
}
|
||||
.rb h4 { font-family: Fraunces, serif; font-style: italic; margin-bottom: 0.25rem; }
|
||||
.rb h5 {
|
||||
font-size: 0.8em; opacity: 0.7;
|
||||
text-transform: uppercase; margin-top: 0.5rem;
|
||||
}
|
||||
.plan-rules ul {
|
||||
list-style: none; padding-left: 0.75rem; margin: 0.25rem 0;
|
||||
border-left: 2px solid var(--color-primary, #6366f1);
|
||||
}
|
||||
.plan-rules li { margin: 0.35rem 0; font-size: 0.92em; }
|
||||
.truncated { opacity: 0.7; font-style: italic; font-size: 0.85em; }
|
||||
</style>
|
||||
@@ -10,6 +10,7 @@ import type { Note } from "@/types/note";
|
||||
import type { TaskStatus } from "@/types/task";
|
||||
import StatusBadge from "@/components/StatusBadge.vue";
|
||||
import PriorityBadge from "@/components/PriorityBadge.vue";
|
||||
import PlanRulesPanel from "@/components/rules/PlanRulesPanel.vue";
|
||||
import TagPill from "@/components/TagPill.vue";
|
||||
import TableOfContents from "@/components/TableOfContents.vue";
|
||||
import ShareDialog from "@/components/ShareDialog.vue";
|
||||
@@ -379,6 +380,12 @@ const subTaskProgress = computed(() => {
|
||||
@click="onBodyClick"
|
||||
></div>
|
||||
|
||||
<!-- Applicable rules (plan tasks only) -->
|
||||
<PlanRulesPanel
|
||||
v-if="store.currentTask.task_kind === 'plan' && store.currentTask.project_id"
|
||||
:project-id="store.currentTask.project_id"
|
||||
/>
|
||||
|
||||
<!-- Sub-tasks -->
|
||||
<div v-if="subTasks.length" class="subtasks">
|
||||
<div class="subtasks-header">
|
||||
|
||||
Reference in New Issue
Block a user