feat: 예약 명령 처리 로직 개선 및 완료 콜백 추가
This commit is contained in:
@@ -2518,10 +2518,13 @@ test('keeps the entered command visible and reports a server validation error',
|
|||||||
await page.getByRole('button', { name: '1턴 명령 입력', exact: true }).click();
|
await page.getByRole('button', { name: '1턴 명령 입력', exact: true }).click();
|
||||||
await page.getByTestId('command-picker').getByRole('button', { name: /화계/ }).click();
|
await page.getByTestId('command-picker').getByRole('button', { name: /화계/ }).click();
|
||||||
await page.getByTestId('command-argument-form').locator('select').selectOption('2');
|
await page.getByTestId('command-argument-form').locator('select').selectOption('2');
|
||||||
await page.getByTestId('command-picker').getByRole('button', { name: '입력', exact: true }).click();
|
const submit = page.getByTestId('command-picker').getByRole('button', { name: '입력', exact: true });
|
||||||
|
await submit.click();
|
||||||
|
|
||||||
await expect(page.getByRole('alert')).toContainText('대상 도시를 선택할 수 없습니다.');
|
await expect(page.getByRole('alert')).toContainText('대상 도시를 선택할 수 없습니다.');
|
||||||
await expect(page.getByTestId('command-argument-form').locator('select')).toHaveValue('2');
|
await expect(page.getByTestId('command-argument-form').locator('select')).toHaveValue('2');
|
||||||
|
await expect(submit).toBeEnabled();
|
||||||
|
await expect(page.getByTestId('command-picker').getByRole('button', { name: '저장 중', exact: true })).toHaveCount(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('keeps Ref command briefs and autonomous-action state after a turn mutation', async ({ page, context }) => {
|
test('keeps Ref command briefs and autonomous-action state after a turn mutation', async ({ page, context }) => {
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import type {
|
|||||||
ReservedCommandRow,
|
ReservedCommandRow,
|
||||||
} from '../command/types';
|
} from '../command/types';
|
||||||
|
|
||||||
|
type ReservationCompletion = (success: boolean) => void;
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
officerLevelText: string;
|
officerLevelText: string;
|
||||||
name: string | null;
|
name: string | null;
|
||||||
@@ -26,10 +28,14 @@ const props = defineProps<{
|
|||||||
const commandRows = computed(() => props.rows.map((row) => ({ ...row, action: row.actionCode ?? row.action })));
|
const commandRows = computed(() => props.rows.map((row) => ({ ...row, action: row.actionCode ?? row.action })));
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: 'reserve-bulk', entries: CommandPatternEntry[]): void;
|
(event: 'reserve-bulk', entries: CommandPatternEntry[], complete?: ReservationCompletion): void;
|
||||||
(event: 'shift', amount: number): void;
|
(event: 'shift', amount: number): void;
|
||||||
(event: 'repeat', amount: number): void;
|
(event: 'repeat', amount: number): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const reserveBulk = (entries: CommandPatternEntry[], complete?: ReservationCompletion) => {
|
||||||
|
emit('reserve-bulk', entries, complete);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -47,7 +53,7 @@ const emit = defineEmits<{
|
|||||||
:current-time="props.rows[0]?.time"
|
:current-time="props.rows[0]?.time"
|
||||||
:map-data="props.mapData"
|
:map-data="props.mapData"
|
||||||
:map-layout="props.mapLayout"
|
:map-layout="props.mapLayout"
|
||||||
@reserve-bulk="emit('reserve-bulk', $event)"
|
@reserve-bulk="reserveBulk"
|
||||||
@shift="emit('shift', $event)"
|
@shift="emit('shift', $event)"
|
||||||
@repeat="emit('repeat', $event)"
|
@repeat="emit('repeat', $event)"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ const props = withDefaults(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: 'reserve-bulk', entries: CommandPatternEntry[]): void;
|
(event: 'reserve-bulk', entries: CommandPatternEntry[], complete?: (success: boolean) => void): void;
|
||||||
(event: 'shift', amount: number): void;
|
(event: 'shift', amount: number): void;
|
||||||
(event: 'repeat', amount: number): void;
|
(event: 'repeat', amount: number): void;
|
||||||
}>();
|
}>();
|
||||||
@@ -79,7 +79,9 @@ const commandArgs = ref<Record<string, unknown>>({});
|
|||||||
const commandArgsValid = ref(false);
|
const commandArgsValid = ref(false);
|
||||||
const expanded = ref(false);
|
const expanded = ref(false);
|
||||||
const menuRevision = ref(0);
|
const menuRevision = ref(0);
|
||||||
const pendingReservation = ref<CommandPatternEntry | null>(null);
|
const pendingReservation = ref<{ requestId: number; entry: CommandPatternEntry } | null>(null);
|
||||||
|
let reservationRequestId = 0;
|
||||||
|
|
||||||
const editorElement = ref<HTMLElement | null>(null);
|
const editorElement = ref<HTMLElement | null>(null);
|
||||||
const pickerElement = ref<HTMLElement | null>(null);
|
const pickerElement = ref<HTMLElement | null>(null);
|
||||||
const collapsedRowCount = 15;
|
const collapsedRowCount = 15;
|
||||||
@@ -109,24 +111,6 @@ watch([editMode, activeCategory], () => {
|
|||||||
if (editMode.value) quickTarget.value = null;
|
if (editMode.value) quickTarget.value = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.rows,
|
|
||||||
(rows) => {
|
|
||||||
const pending = pendingReservation.value;
|
|
||||||
if (!pending) return;
|
|
||||||
const saved = pending.turnList.every((index) => {
|
|
||||||
const row = rows[index];
|
|
||||||
return row?.action === pending.action && JSON.stringify(row.args ?? {}) === JSON.stringify(pending.args);
|
|
||||||
});
|
|
||||||
if (!saved) return;
|
|
||||||
storage.value?.pushRecent({ ...pending, turnList: [0] });
|
|
||||||
pendingReservation.value = null;
|
|
||||||
releaseSelection();
|
|
||||||
closePicker();
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
const scopedTable = computed<CommandTable | null>(() => {
|
const scopedTable = computed<CommandTable | null>(() => {
|
||||||
if (!props.commandTable) return null;
|
if (!props.commandTable) return null;
|
||||||
return {
|
return {
|
||||||
@@ -276,11 +260,23 @@ const selectCommand = (commandKey: string) => {
|
|||||||
};
|
};
|
||||||
const submitCommand = () => {
|
const submitCommand = () => {
|
||||||
const command = selectedCommand.value;
|
const command = selectedCommand.value;
|
||||||
if (!command || !commandArgsValid.value) return;
|
if (!command || !commandArgsValid.value || pendingReservation.value) return;
|
||||||
const turnList = quickTarget.value === null ? selectedIndices() : [quickTarget.value];
|
const turnList = quickTarget.value === null ? selectedIndices() : [quickTarget.value];
|
||||||
const entry = { turnList, action: command.key, args: { ...commandArgs.value }, label: command.name };
|
const entry = { turnList, action: command.key, args: { ...commandArgs.value }, label: command.name };
|
||||||
emit('reserve-bulk', [entry]);
|
const requestId = ++reservationRequestId;
|
||||||
pendingReservation.value = entry;
|
pendingReservation.value = { requestId, entry };
|
||||||
|
|
||||||
|
emit('reserve-bulk', [entry], (success) => {
|
||||||
|
const pending = pendingReservation.value;
|
||||||
|
if (!pending || pending.requestId !== requestId) return;
|
||||||
|
|
||||||
|
pendingReservation.value = null;
|
||||||
|
if (!success) return;
|
||||||
|
|
||||||
|
storage.value?.pushRecent({ ...entry, turnList: [0] });
|
||||||
|
releaseSelection();
|
||||||
|
closePicker();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
const returnToCommandList = () => {
|
const returnToCommandList = () => {
|
||||||
selectedCommand.value = null;
|
selectedCommand.value = null;
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import type {
|
|||||||
ReservedCommandRow,
|
ReservedCommandRow,
|
||||||
} from '../command/types';
|
} from '../command/types';
|
||||||
|
|
||||||
|
type ReservationCompletion = (success: boolean) => void;
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
commandTable: CommandTable | null;
|
commandTable: CommandTable | null;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@@ -35,11 +37,15 @@ const props = defineProps<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: 'set-general-turns', entries: CommandPatternEntry[]): void;
|
(event: 'set-general-turns', entries: CommandPatternEntry[], complete?: ReservationCompletion): void;
|
||||||
(event: 'shift-general-turns', amount: number): void;
|
(event: 'shift-general-turns', amount: number): void;
|
||||||
(event: 'repeat-general-turns', amount: number): void;
|
(event: 'repeat-general-turns', amount: number): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const reserveBulk = (entries: CommandPatternEntry[], complete?: ReservationCompletion) => {
|
||||||
|
emit('set-general-turns', entries, complete);
|
||||||
|
};
|
||||||
|
|
||||||
const editModeStorageKey = generalTurnEditorModeStorageKey(
|
const editModeStorageKey = generalTurnEditorModeStorageKey(
|
||||||
gameFrontendRuntimeConfig.profile,
|
gameFrontendRuntimeConfig.profile,
|
||||||
gameFrontendRuntimeConfig.appBasePath
|
gameFrontendRuntimeConfig.appBasePath
|
||||||
@@ -154,7 +160,7 @@ onUnmounted(() => {
|
|||||||
:autonomous-until="autonomousUntil"
|
:autonomous-until="autonomousUntil"
|
||||||
:mobile="props.mobile"
|
:mobile="props.mobile"
|
||||||
:max-push-turn="12"
|
:max-push-turn="12"
|
||||||
@reserve-bulk="emit('set-general-turns', $event)"
|
@reserve-bulk="reserveBulk"
|
||||||
@shift="emit('shift-general-turns', $event)"
|
@shift="emit('shift-general-turns', $event)"
|
||||||
@repeat="emit('repeat-general-turns', $event)"
|
@repeat="emit('repeat-general-turns', $event)"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1041,9 +1041,9 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
|||||||
|
|
||||||
const setGeneralTurns = async (
|
const setGeneralTurns = async (
|
||||||
entries: Array<{ turnList: number[]; action: string; args: Record<string, unknown> }>
|
entries: Array<{ turnList: number[]; action: string; args: Record<string, unknown> }>
|
||||||
) => {
|
): Promise<boolean> => {
|
||||||
const id = generalId.value;
|
const id = generalId.value;
|
||||||
if (!id || !entries.length) return;
|
if (!id || !entries.length) return false;
|
||||||
try {
|
try {
|
||||||
const result = await trpc.turns.reserved.setGeneralBulk.mutate({
|
const result = await trpc.turns.reserved.setGeneralBulk.mutate({
|
||||||
generalId: id,
|
generalId: id,
|
||||||
@@ -1053,6 +1053,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
|||||||
reservedGeneralTurns.value = result.turns;
|
reservedGeneralTurns.value = result.turns;
|
||||||
reservedGeneralRevision.value = result.revision;
|
reservedGeneralRevision.value = result.revision;
|
||||||
reservedGeneralAutorunLimit.value = result.autorunLimit ?? null;
|
reservedGeneralAutorunLimit.value = result.autorunLimit ?? null;
|
||||||
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error.value = resolveErrorMessage(err);
|
error.value = resolveErrorMessage(err);
|
||||||
const snapshot = await trpc.turns.reserved.getGeneral.query({ generalId: id }).catch(() => null);
|
const snapshot = await trpc.turns.reserved.getGeneral.query({ generalId: id }).catch(() => null);
|
||||||
@@ -1061,6 +1062,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
|||||||
reservedGeneralRevision.value = snapshot.revision;
|
reservedGeneralRevision.value = snapshot.revision;
|
||||||
reservedGeneralAutorunLimit.value = snapshot.autorunLimit ?? null;
|
reservedGeneralAutorunLimit.value = snapshot.autorunLimit ?? null;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { formatOfficerLevelText } from '../utils/nationFormat';
|
|||||||
import { formatReservedCommandBrief } from '../components/command/reservedCommandBrief';
|
import { formatReservedCommandBrief } from '../components/command/reservedCommandBrief';
|
||||||
import type { CommandMapData, CommandMapLayout, CommandPatternEntry, CommandTable } from '../components/command/types';
|
import type { CommandMapData, CommandMapLayout, CommandPatternEntry, CommandTable } from '../components/command/types';
|
||||||
|
|
||||||
|
type ReservationCompletion = (success: boolean) => void;
|
||||||
|
|
||||||
type ChiefTurn = {
|
type ChiefTurn = {
|
||||||
index: number;
|
index: number;
|
||||||
action: string;
|
action: string;
|
||||||
@@ -284,8 +286,11 @@ const shiftTurns = async (amount: number) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const reserveTurns = async (entries: CommandPatternEntry[]) => {
|
const reserveTurns = async (entries: CommandPatternEntry[], complete?: ReservationCompletion) => {
|
||||||
if (!data.value || !isEditingAllowed.value) return;
|
if (!data.value || !isEditingAllowed.value) {
|
||||||
|
complete?.(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const result = await trpc.turns.reserved.setNationBulk.mutate({
|
const result = await trpc.turns.reserved.setNationBulk.mutate({
|
||||||
generalId: data.value.me.id,
|
generalId: data.value.me.id,
|
||||||
@@ -293,9 +298,11 @@ const reserveTurns = async (entries: CommandPatternEntry[]) => {
|
|||||||
expectedRevision: selectedChief.value?.revision ?? 0,
|
expectedRevision: selectedChief.value?.revision ?? 0,
|
||||||
});
|
});
|
||||||
updateMyTurns(result.turns, result.revision);
|
updateMyTurns(result.turns, result.revision);
|
||||||
|
complete?.(true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await loadChiefCenter();
|
await loadChiefCenter();
|
||||||
error.value = resolveErrorMessage(err);
|
error.value = resolveErrorMessage(err);
|
||||||
|
complete?.(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -183,8 +183,12 @@ const shiftGeneralTurns = (amount: number) => {
|
|||||||
void dashboard.shiftGeneralTurns(amount);
|
void dashboard.shiftGeneralTurns(amount);
|
||||||
};
|
};
|
||||||
|
|
||||||
const reserveGeneralTurns = (entries: CommandPatternEntry[]) => {
|
const reserveGeneralTurns = async (
|
||||||
void dashboard.setGeneralTurns(entries);
|
entries: CommandPatternEntry[],
|
||||||
|
complete?: (success: boolean) => void
|
||||||
|
) => {
|
||||||
|
const success = await dashboard.setGeneralTurns(entries);
|
||||||
|
complete?.(success);
|
||||||
};
|
};
|
||||||
|
|
||||||
const repeatGeneralTurns = (amount: number) => {
|
const repeatGeneralTurns = (amount: number) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user