cleanup rules

This commit is contained in:
Sirttas
2026-05-31 18:57:10 +02:00
parent 1358aaa705
commit 457d2a5161
6 changed files with 79 additions and 35 deletions
+2 -2
View File
@@ -367,12 +367,12 @@ components:
RuleResponse: RuleResponse:
type: object type: object
properties: properties:
rules: clauses:
type: array type: array
items: items:
$ref: "#/components/schemas/RuleClauseResponse" $ref: "#/components/schemas/RuleClauseResponse"
required: required:
- rules - clauses
UpdateRuleBookRequest: UpdateRuleBookRequest:
type: object type: object
properties: properties:
+28 -9
View File
@@ -1,22 +1,37 @@
<script setup lang="ts"> <script setup lang="ts">
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/24/outline'; import {ChevronDownIcon, ChevronUpIcon} from '@heroicons/vue/24/outline';
import { vOnClickOutside } from '@vueuse/components'; import {vOnClickOutside} from '@vueuse/components';
import { useEventListener } from '@vueuse/core'; import {useEventListener} from '@vueuse/core';
import { ref } from 'vue'; import {ref} from 'vue';
interface Props {
inline?: boolean;
autoClose: boolean;
}
const props = withDefaults(defineProps<Props>(), {
inline: false,
autoClose: true
})
const isOpen = ref(false); const isOpen = ref(false);
const doAutoClose = () => {
if (props.autoClose) {
isOpen.value = false;
}
}
useEventListener('keyup', e => { useEventListener('keyup', e => {
if (e.key === 'Escape') { if (e.key === 'Escape') {
isOpen.value = false; doAutoClose();
} }
}); });
</script> </script>
<template> <template>
<div class="dropdown" :class="{'dropdown-open': isOpen, 'dropdown-close': !isOpen}" v-on-click-outside="() => isOpen = false"> <div class="dropdown" :class="{'dropdown-open': isOpen, 'dropdown-close': !isOpen}" v-on-click-outside="doAutoClose">
<button @click="isOpen = !isOpen"> <button @click="isOpen = !isOpen">
<slot name="button" />
<Transition <Transition
enter-active-class="transition-transform" enter-active-class="transition-transform"
enter-from-class="rotate-180" enter-from-class="rotate-180"
@@ -25,6 +40,7 @@ useEventListener('keyup', e => {
<ChevronDownIcon v-if="!isOpen" class="chevron" /> <ChevronDownIcon v-if="!isOpen" class="chevron" />
<ChevronUpIcon v-else class="chevron" /> <ChevronUpIcon v-else class="chevron" />
</Transition> </Transition>
<slot name="button" />
</button> </button>
<Transition <Transition
@@ -32,7 +48,10 @@ useEventListener('keyup', e => {
enter-from-class="opacity-0" enter-from-class="opacity-0"
leave-from-class="transition-opacity" leave-from-class="transition-opacity"
leave-to-class="opacity-0"> leave-to-class="opacity-0">
<div v-if="isOpen" class="relative"> <div v-if="inline && isOpen">
<slot />
</div>
<div v-else-if="isOpen" class="relative">
<div class="z-10 divide-y rounded-b-md absolute"> <div class="z-10 divide-y rounded-b-md absolute">
<slot /> <slot />
</div> </div>
@@ -45,6 +64,6 @@ useEventListener('keyup', e => {
@reference "tailwindcss"; @reference "tailwindcss";
.chevron { .chevron {
@apply w-4 h-4 ms-1; @apply w-4 h-4 me-1;
} }
</style> </style>
+1 -1
View File
@@ -110,7 +110,7 @@ export const RuleClauseResponseRateEnum = {
export type RuleClauseResponseRateEnum = typeof RuleClauseResponseRateEnum[keyof typeof RuleClauseResponseRateEnum]; export type RuleClauseResponseRateEnum = typeof RuleClauseResponseRateEnum[keyof typeof RuleClauseResponseRateEnum];
export interface RuleResponse { export interface RuleResponse {
'rules': Array<RuleClauseResponse>; 'clauses': Array<RuleClauseResponse>;
} }
export interface SetCharacterRuleBookRequest { export interface SetCharacterRuleBookRequest {
'ruleBookId': string; 'ruleBookId': string;
+20 -2
View File
@@ -6,6 +6,7 @@ import log from "loglevel";
import {activityTypes, RuleInput, Rules, useRuleBooksStore} from "@/rules"; import {activityTypes, RuleInput, Rules, useRuleBooksStore} from "@/rules";
import {PlusIcon, TrashIcon} from "@heroicons/vue/24/outline"; import {PlusIcon, TrashIcon} from "@heroicons/vue/24/outline";
import {routeNames} from "@/routes"; import {routeNames} from "@/routes";
import {Dropdown} from "@/components";
const ruleBookId = ref<string>(); const ruleBookId = ref<string>();
const name = ref<string>(''); const name = ref<string>('');
@@ -89,8 +90,13 @@ watch(useRoute(), async route => {
</div> </div>
</div> </div>
<div class="flex flex-col grow border-b-1" v-for="activityType in activityTypes" :key="activityType.key"> <div class="flex flex-col grow border-b-1" v-for="activityType in activityTypes" :key="activityType.key">
<span>{{ activityType.name }}</span> <Dropdown :inline="true" :autoClose="false" class="rule-dropdown">
<RuleInput :ledgerRefs="ledgerRefs" v-model="rules[activityType.key]" /> <template #button>
<span>{{ activityType.name }}</span>
</template>
<RuleInput :ledgerRefs="ledgerRefs" v-model="rules[activityType.key]" />
</Dropdown>
</div> </div>
</div> </div>
<div class="mt-2 justify-end flex"> <div class="mt-2 justify-end flex">
@@ -100,3 +106,15 @@ watch(useRoute(), async route => {
</div> </div>
</div> </div>
</template> </template>
<style scoped>
@reference "tailwindcss";
.rule-dropdown :deep(>button) {
@apply bg-slate-800 hover:bg-slate-800 border-none flex items-center w-full;
}
.rule-dropdown.dropdown-open :deep(>button) {
@apply bg-slate-800 rounded-b-none;
}
</style>
+22 -17
View File
@@ -5,6 +5,7 @@ import RuleClauseInput from "@/rules/RuleClauseInput.vue";
import {computed, useTemplateRef} from "vue"; import {computed, useTemplateRef} from "vue";
import {Bars4Icon, PlusIcon, TrashIcon} from '@heroicons/vue/24/outline'; import {Bars4Icon, PlusIcon, TrashIcon} from '@heroicons/vue/24/outline';
import {useSortable} from "@vueuse/integrations/useSortable"; import {useSortable} from "@vueuse/integrations/useSortable";
import {systemLedgerRef} from "@/ledger";
interface Props { interface Props {
ledgerRefs: string[]; ledgerRefs: string[];
@@ -12,49 +13,54 @@ interface Props {
const props = defineProps<Props>() const props = defineProps<Props>()
const rule = defineModel<RuleResponse>({default: {rules:{}}}); const rule = defineModel<RuleResponse>({default: {clauses:[]}});
const rules = computed<RuleClauseResponse[]>({ const clauses = computed<RuleClauseResponse[]>({
get: () => rule.value && rule.value.rules ? rule.value.rules : [], get: () => rule.value && rule.value.clauses ? rule.value.clauses : [],
set: value => rule.value = {rules: value} set: value => rule.value = {clauses: value}
}) })
const addRule = () => { const addClause = () => {
rules.value = [...rules.value, {rate: RuleClauseResponseRateEnum.None}] clauses.value = [...clauses.value, {
rate: RuleClauseResponseRateEnum.None,
fromLedgerRef: systemLedgerRef,
toLedgerRef: systemLedgerRef
}]
} }
const setRule = (index: number, rule?: RuleClauseResponse) => { const setClause = (index: number, clause?: RuleClauseResponse) => {
if (!rule) { if (!clause) {
return; return;
} }
rules.value = rules.value.with(index, rule) clauses.value = clauses.value.with(index, clause)
} }
const removeRule = (index: number) => { const removeClause = (index: number) => {
rules.value = rules.value.toSpliced(index, 1) clauses.value = clauses.value.toSpliced(index, 1)
} }
const sortableContainer = useTemplateRef('sortable-container') const sortableContainer = useTemplateRef('sortable-container')
useSortable(sortableContainer, rules, { handle: '.sortable-handle'}); useSortable(sortableContainer, clauses, { handle: '.sortable-handle'});
</script> </script>
<template> <template>
<div class="flex-col"> <div class="flex-col">
<div ref="sortable-container" class="flex-col"> <div ref="sortable-container" class="flex-col">
<div class="flex items-end gap-2 mt-2" v-for="(rule, index) in rules" :key="index"> <div class="flex items-end gap-2 mt-2" v-for="(clause, index) in clauses" :key="index">
<span class="sortable-handle flex"> <span class="sortable-handle flex">
<Bars4Icon class="w-6"/> <Bars4Icon class="w-6"/>
</span> </span>
<RuleClauseInput :ledgerRefs="ledgerRefs" :modelValue="rule" @update:modelValue="v => setRule(index, v)" /> <RuleClauseInput :ledgerRefs="ledgerRefs" :modelValue="clause" @update:modelValue="v => setClause(index, v)" />
<button class="btn-icon" @click="removeRule(index)"><TrashIcon /></button> <button class="btn-icon" @click="removeClause(index)"><TrashIcon /></button>
</div> </div>
</div> </div>
<div class="flex justify-end mb-2 mt-2"> <div class="flex justify-end mb-2 mt-2">
<button class="btn-icon" @click="addRule"><PlusIcon /></button> <button class="btn-icon" @click="addClause"><PlusIcon /></button>
</div> </div>
</div> </div>
</template> </template>
<style scoped> <style scoped>
@reference "tailwindcss";
.sortable-handle { .sortable-handle {
@apply cursor-grab; @apply cursor-grab;
@@ -67,5 +73,4 @@ useSortable(sortableContainer, rules, { handle: '.sortable-handle'});
.sortable-chosen .sortable-handle { .sortable-chosen .sortable-handle {
@apply cursor-grabbing; @apply cursor-grabbing;
} }
</style> </style>
+4 -2
View File
@@ -1,4 +1,4 @@
import {characterRuleBookApi, ledgerApi, ruleBookApi} from "@/mammon"; import {characterRuleBookApi, ruleBookApi} from "@/mammon";
import { import {
CharacterRuleBookResponse, CharacterRuleBookResponse,
CreateRuleBookRequest, CreateRuleBookRequest,
@@ -13,6 +13,8 @@ import {ref, triggerRef} from "vue";
export const activityTypes = { export const activityTypes = {
itemBought: {key: "ITEM_BOUGHT", name: "Item Bought"}, itemBought: {key: "ITEM_BOUGHT", name: "Item Bought"},
itemSold: {key: "ITEM_SOLD", name: "Item Sold"}, itemSold: {key: "ITEM_SOLD", name: "Item Sold"},
itemAcquiredManually: {key: "ITEM_ACQUIRED_MANUALLY", name: "Item Acquired Manually"},
itemConsumedManually: {key: "ITEM_CONSUME_MANUALLY", name: "Item Consumed Manually"},
// bountyEarned: {id: "BOUNTY_EARNED", name: "Bounty Earned"}, // bountyEarned: {id: "BOUNTY_EARNED", name: "Bounty Earned"},
// itemManufactured: {id: "ITEM_MANUFACTURED", name: "Item Manufactured"} // itemManufactured: {id: "ITEM_MANUFACTURED", name: "Item Manufactured"}
} as const; } as const;
@@ -53,7 +55,7 @@ export const useRuleBooksStore = defineStore('rule-books', () => {
const findById = (ruleBookId: string): RuleBook | undefined => ruleBooks.value.find(rb => rb.ruleBookId === ruleBookId); const findById = (ruleBookId: string): RuleBook | undefined => ruleBooks.value.find(rb => rb.ruleBookId === ruleBookId);
const create = (ruleBook: CreateRuleBookRequest) => ruleBookApi.createRuleBook(ruleBook).then(response => addRuleBook(response.data)); const create = (ruleBook: CreateRuleBookRequest) => ruleBookApi.createRuleBook(ruleBook).then(response => addRuleBook(response.data));
const update = (ruleBookId: string, ruleBook: CreateRuleBookRequest) => ledgerApi.updateMainLedger(ruleBookId, ruleBook).then(response => replaceRuleBook(response.data)); const update = (ruleBookId: string, ruleBook: CreateRuleBookRequest) => ruleBookApi.updateRuleBook(ruleBookId, ruleBook).then(response => replaceRuleBook(response.data));
const refresh = () => ruleBookApi.findAllRuleBooks().then(response => ruleBooks.value = response.data as RuleBook[]); const refresh = () => ruleBookApi.findAllRuleBooks().then(response => ruleBooks.value = response.data as RuleBook[]);