-
+
+
{{ CURRENCY_SYMBOL[state.budgets[rowIndex].currency] }}
- {{ Math.abs(Number((Number(item.limit) - Number(item.actualSpend)).toFixed(2))).toLocaleString() }}
+ {{ Math.abs(value.toFixed(2)).toLocaleString() }}
-
-
+
+
{{ $t('BILLING.COST_MANAGEMENT.BUDGET.MAIN.SCHEDULED') }}
-
+
{{ $t('BILLING.COST_MANAGEMENT.BUDGET.MAIN.EXPIRED') }}
budgetPageStore.$state.budgetData);
diff --git a/apps/web/src/services/cost-explorer/components/BudgetUsageTrendMonthlyTable.vue b/apps/web/src/services/cost-explorer/components/BudgetUsageTrendMonthlyTable.vue
index 872926ab6e..d61e7ff762 100644
--- a/apps/web/src/services/cost-explorer/components/BudgetUsageTrendMonthlyTable.vue
+++ b/apps/web/src/services/cost-explorer/components/BudgetUsageTrendMonthlyTable.vue
@@ -11,8 +11,8 @@ import { i18n } from '@/translations';
import { useUserWorkspaceStore } from '@/store/app-context/workspace/user-workspace-store';
import { CURRENCY_SYMBOL } from '@/store/display/constant';
-import { COST_EXPLORER_ROUTE } from '../routes/route-constant';
-import { useBudgetDetailPageStore } from '../stores/budget-detail-page-store';
+import { COST_EXPLORER_ROUTE } from '@/services/cost-explorer/routes/route-constant';
+import { useBudgetDetailPageStore } from '@/services/cost-explorer/stores/budget-detail-page-store';
interface Props {
data: any;
@@ -159,7 +159,7 @@ const handleToggleOriginalData = (value: boolean) => {
-
+
{{ CURRENCY_SYMBOL[budgetData?.currency ?? 'KRW'] }}
diff --git a/apps/web/src/services/cost-explorer/components/BudgetUsageTrendTotal.vue b/apps/web/src/services/cost-explorer/components/BudgetUsageTrendTotal.vue
index d99fa0ba8a..271b6dae27 100644
--- a/apps/web/src/services/cost-explorer/components/BudgetUsageTrendTotal.vue
+++ b/apps/web/src/services/cost-explorer/components/BudgetUsageTrendTotal.vue
@@ -5,9 +5,9 @@ import { SpaceConnector } from '@cloudforet/core-lib/space-connector';
import ErrorHandler from '@/common/composables/error/errorHandler';
-import { useBudgetDetailPageStore } from '../stores/budget-detail-page-store';
-import BudgetUsageTrendTotalChart from './BudgetUsageTrendTotalChart.vue';
-import BudgetUsageTrendTotalTable from './BudgetUsageTrendTotalTable.vue';
+import BudgetUsageTrendTotalChart from '@/services/cost-explorer/components/BudgetUsageTrendTotalChart.vue';
+import BudgetUsageTrendTotalTable from '@/services/cost-explorer/components/BudgetUsageTrendTotalTable.vue';
+import { useBudgetDetailPageStore } from '@/services/cost-explorer/stores/budget-detail-page-store';
const budgetPageStore = useBudgetDetailPageStore();
const budgetPageState = budgetPageStore.$state;
diff --git a/apps/web/src/services/cost-explorer/components/BudgetUsageTrendTotalChart.vue b/apps/web/src/services/cost-explorer/components/BudgetUsageTrendTotalChart.vue
index e8f0126c55..1790b9f8e3 100644
--- a/apps/web/src/services/cost-explorer/components/BudgetUsageTrendTotalChart.vue
+++ b/apps/web/src/services/cost-explorer/components/BudgetUsageTrendTotalChart.vue
@@ -15,7 +15,7 @@ import {
gray, indigo, peacock, red,
} from '@/styles/colors';
-import { useBudgetDetailPageStore } from '../stores/budget-detail-page-store';
+import { useBudgetDetailPageStore } from '@/services/cost-explorer/stores/budget-detail-page-store';
const budgetpageStore = useBudgetDetailPageStore();
const budgetPageState = budgetpageStore.$state;
@@ -54,7 +54,7 @@ const state = reactive({
params.forEach((param) => {
const value = Number(param.data);
- const formatted = Number.isNaN(value) ? '-' : value.toLocaleString(undefined, { maximumFractionDigits: 2 });
+ const formatted = Number.isNaN(value) ? '-' : value.toLocaleString(undefined, { maximumFractionDigits: 3 });
if (param.seriesName === 'Accumulated Usage (under Planned Budget)' || param.seriesName === 'Accumulated Usage (over Planned Budget)') {
if (accumulatedValue === null || value > Number(accumulatedValue.replace(/,/g, ''))) {
@@ -65,6 +65,8 @@ const state = reactive({
accumulatedColor = peacock[400];
}
}
+ } else if (param.seriesName === 'Actual Spend') {
+ tooltipLines.push(`${param.marker} ${param.seriesName}: ${param.data}`);
} else {
tooltipLines.push(`${param.marker} ${param.seriesName}: ${formatted}`);
}
@@ -183,7 +185,7 @@ const drawChart = (rawData: {
label: {
show: false,
},
- data: data.map((item) => Number(item.budget_usage).toFixed(2)),
+ data: data.map((item) => Number(item.budget_usage).toFixed(3)),
color: indigo[400],
},
{
@@ -249,7 +251,7 @@ watch([() => state.data, () => budgetPageState], () => {
.chart {
height: 100%;
- min-width: 700px;
+ min-width: 62.5rem;
width: 100%;
}
}
diff --git a/apps/web/src/services/cost-explorer/components/BudgetUsageTrendTotalTable.vue b/apps/web/src/services/cost-explorer/components/BudgetUsageTrendTotalTable.vue
index 7b5ac14a88..f1d02bcf28 100644
--- a/apps/web/src/services/cost-explorer/components/BudgetUsageTrendTotalTable.vue
+++ b/apps/web/src/services/cost-explorer/components/BudgetUsageTrendTotalTable.vue
@@ -12,9 +12,9 @@ import { i18n } from '@/translations';
import { useAppContextStore } from '@/store/app-context/app-context-store';
import { CURRENCY_SYMBOL } from '@/store/display/constant';
-import { ADMIN_COST_EXPLORER_ROUTE } from '../routes/admin/route-constant';
-import { COST_EXPLORER_ROUTE } from '../routes/route-constant';
-import { useBudgetDetailPageStore } from '../stores/budget-detail-page-store';
+import { ADMIN_COST_EXPLORER_ROUTE } from '@/services/cost-explorer/routes/admin/route-constant';
+import { COST_EXPLORER_ROUTE } from '@/services/cost-explorer/routes/route-constant';
+import { useBudgetDetailPageStore } from '@/services/cost-explorer/stores/budget-detail-page-store';
interface Props {
data: any;
@@ -192,7 +192,8 @@ const handleToggleOriginalData = (value: boolean) => {
class="flex items-center gap-2"
>
{{ value }}
-
+
+
Date: Tue, 22 Apr 2025 15:30:48 +0900
Subject: [PATCH 3/3] feat: update budget create UI about duplicated month
(#5795)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat: update languages
Signed-off-by: 이승연
* feat: update about duplicated month of existing budget
Signed-off-by: 이승연
* fix: fix minor bug
Signed-off-by: 이승연
---------
Signed-off-by: 이승연
---
.../budget/schema/api-verbs/create.ts | 1 -
.../cost-analysis/budget/schema/model.ts | 1 -
.../components/BudgetCreateStep1.vue | 16 -----
.../components/BudgetCreateStep2.vue | 69 ++++++++++---------
.../components/BudgetCreateStep3.vue | 9 ++-
.../BudgetUsageTrendMonthlyChart.vue | 45 +++++++++---
.../components/BudgetUsageTrendTotalChart.vue | 1 +
.../console-translation-2.8.babel | 12 ++--
packages/language-pack/en.json | 4 +-
packages/language-pack/ja.json | 4 +-
packages/language-pack/ko.json | 4 +-
11 files changed, 91 insertions(+), 75 deletions(-)
diff --git a/apps/web/src/api-clients/cost-analysis/budget/schema/api-verbs/create.ts b/apps/web/src/api-clients/cost-analysis/budget/schema/api-verbs/create.ts
index fcd2ec3583..1f4083e1ab 100644
--- a/apps/web/src/api-clients/cost-analysis/budget/schema/api-verbs/create.ts
+++ b/apps/web/src/api-clients/cost-analysis/budget/schema/api-verbs/create.ts
@@ -21,5 +21,4 @@ export interface BudgetCreateParameters {
project_id?: string;
service_account_id?: string;
budget_manager_id?: string;
- budget_year?: string;
}
diff --git a/apps/web/src/api-clients/cost-analysis/budget/schema/model.ts b/apps/web/src/api-clients/cost-analysis/budget/schema/model.ts
index 42abdf7f5d..0914af4cf6 100644
--- a/apps/web/src/api-clients/cost-analysis/budget/schema/model.ts
+++ b/apps/web/src/api-clients/cost-analysis/budget/schema/model.ts
@@ -30,7 +30,6 @@ export interface BudgetModel {
domain_id: string;
created_at: string;
updated_at: string;
- budget_year: string;
notified_month: string;
utilization_rate: number;
state: BudgetState;
diff --git a/apps/web/src/services/cost-explorer/components/BudgetCreateStep1.vue b/apps/web/src/services/cost-explorer/components/BudgetCreateStep1.vue
index c1412c9de8..3b91cc8050 100644
--- a/apps/web/src/services/cost-explorer/components/BudgetCreateStep1.vue
+++ b/apps/web/src/services/cost-explorer/components/BudgetCreateStep1.vue
@@ -149,22 +149,6 @@ watch(() => budgetCreatePageState.project, async () => {
watchEffect(async () => {
await fetchBudget();
});
-
-watch([
- () => state.budgetList,
- () => budgetCreatePageState.project,
- () => budgetCreatePageState.scope.serviceAccount,
- () => budgetCreatePageState.scope.type,
-], () => {
- const filteredList = state.budgetList.filter((result) => {
- if (budgetCreatePageState.scope.type === 'project') {
- return result.project_id === budgetCreatePageState.project && !result.service_account_id;
- }
- return result.project_id === budgetCreatePageState.project
- && result.service_account_id === budgetCreatePageState.scope.serviceAccount;
- });
- budgetCreatePageState.alreadyExistingBudgetYear = filteredList.map((result) => result.budget_year);
-}, { deep: true, immediate: true });
diff --git a/apps/web/src/services/cost-explorer/components/BudgetCreateStep2.vue b/apps/web/src/services/cost-explorer/components/BudgetCreateStep2.vue
index 51a000f1ed..f3eb7d7784 100644
--- a/apps/web/src/services/cost-explorer/components/BudgetCreateStep2.vue
+++ b/apps/web/src/services/cost-explorer/components/BudgetCreateStep2.vue
@@ -7,7 +7,7 @@ import dayjs from 'dayjs';
import { SpaceConnector } from '@cloudforet/core-lib/space-connector';
import {
- PFieldGroup, PSelectDropdown, PDatetimePicker, PButton, PStatus,
+ PFieldGroup, PSelectDropdown, PDatetimePicker, PButton,
PDivider, PRadioGroup, PRadio, PPaneLayout, PTextInput, PBadge,
} from '@cloudforet/mirinae';
@@ -248,6 +248,11 @@ watch(() => [
budgetCreatePageState.startMonth,
budgetCreatePageState.endMonth,
], () => {
+ if (state.existingBudgetUsageList.length > 0) {
+ state.isContinueAble = false;
+ return;
+ }
+
if (budgetCreatePageState.startMonth.length === 0 || budgetCreatePageState.endMonth.length === 0) {
state.isContinueAble = false;
}
@@ -345,6 +350,17 @@ watch(() => budgetCreatePageState.limit, () => {
budgetCreatePageState.limit = undefined;
}
}, { immediate: true });
+
+watch(() => state.existingBudgetUsageList, () => {
+ if (state.existingBudgetUsageList.length > 0) {
+ budgetCreatePageState.time_unit = '';
+ budgetCreatePageState.limit = undefined;
+ budgetCreatePageState.budgetAppliedSameAmount = undefined;
+ budgetCreatePageState.initialAmount = undefined;
+ budgetCreatePageState.monthlyGrowthRate = undefined;
+ budgetCreatePageState.budgetEachDate = [];
+ }
+}, { deep: true, immediate: true });
@@ -386,18 +402,22 @@ watch(() => budgetCreatePageState.limit, () => {
-
+
budgetCreatePageState.limit, () => {
:min-date="budgetCreatePageState.startMonth.length > 0
? dayjs.utc(budgetCreatePageState.startMonth[0]).add(1, 'month').format('YYYY-MM') : ''"
:max-date="budgetCreatePageState.startMonth.length > 0 ? dayjs.utc(budgetCreatePageState.startMonth[0]).add(11, 'month').format('YYYY-MM') : ''"
+ :invalid="state.existingBudgetUsageList.length > 0"
/>
-
-
-
+
+ {{ budgetCreatePageState.scope.type === 'project'
+ ? $t('BILLING.COST_MANAGEMENT.BUDGET.FORM.CREATE.PROJECT_DUPLICATED_WARNING1', {
+ project: project[budgetCreatePageState.project].name,
+ month_list: state.existingBudgetUsageList.map(d => d.date)
+ }) : $t('BILLING.COST_MANAGEMENT.BUDGET.FORM.CREATE.PROJECT_DUPLICATED_WARNING2', {
+ project: project[budgetCreatePageState.project].name,
+ serviceAccount: serviceAccount[budgetCreatePageState.scope.serviceAccount ?? ''].name,
+ month_list: state.existingBudgetUsageList.map(d => d.date)
+ }) }}
+
+
{{ cycle.label }}
diff --git a/apps/web/src/services/cost-explorer/components/BudgetCreateStep3.vue b/apps/web/src/services/cost-explorer/components/BudgetCreateStep3.vue
index 43c67d1cb8..bdc0fc1829 100644
--- a/apps/web/src/services/cost-explorer/components/BudgetCreateStep3.vue
+++ b/apps/web/src/services/cost-explorer/components/BudgetCreateStep3.vue
@@ -18,7 +18,6 @@ import { useUserReferenceStore } from '@/store/reference/user-reference-store';
import { showErrorMessage, showSuccessMessage } from '@/lib/helper/notice-alert-helper';
-import ErrorHandler from '@/common/composables/error/errorHandler';
import UserSelectDropdown from '@/common/modules/user/UserSelectDropdown.vue';
import { COST_EXPLORER_ROUTE } from '@/services/cost-explorer/routes/route-constant';
@@ -101,7 +100,6 @@ const createBudget = async (type: 'skip' | 'set') => {
time_unit: budgetCreatePageState.time_unit === 'TOTAL' ? 'TOTAL' : 'MONTHLY',
start: dayjs.utc(budgetCreatePageState.startMonth[0]).format('YYYY-MM'),
end: dayjs.utc(budgetCreatePageState.endMonth[0]).format('YYYY-MM'),
- budget_year: budgetCreatePageState.budgetYear,
notification: type === 'set' ? {
state: budgetCreatePageState.thresholds.filter((threshold) => threshold.value && threshold.value > 0).length > 0
? 'ENABLED' : 'DISABLED',
@@ -128,11 +126,12 @@ const createBudget = async (type: 'skip' | 'set') => {
name: COST_EXPLORER_ROUTE.BUDGET._NAME,
});
showSuccessMessage(i18n.t('BILLING.COST_MANAGEMENT.BUDGET.ALT_S_CREATE_BUDGET'), '');
+ budgetCreatePageStore.reset();
} catch (error: any) {
- ErrorHandler.handleError(error, true);
- showErrorMessage(error?.code ?? '', i18n.t('BILLING.COST_MANAGEMENT.BUDGET.ALT_E_CREATE_BUDGET'));
+ showErrorMessage(i18n.t('BILLING.COST_MANAGEMENT.BUDGET.ALT_E_CREATE_BUDGET'), error.message);
+ budgetCreatePageState.currentStep = 1;
} finally {
- budgetCreatePageStore.reset();
+ budgetCreatePageState.loading = false;
}
};
diff --git a/apps/web/src/services/cost-explorer/components/BudgetUsageTrendMonthlyChart.vue b/apps/web/src/services/cost-explorer/components/BudgetUsageTrendMonthlyChart.vue
index d6c266aad7..332327fa13 100644
--- a/apps/web/src/services/cost-explorer/components/BudgetUsageTrendMonthlyChart.vue
+++ b/apps/web/src/services/cost-explorer/components/BudgetUsageTrendMonthlyChart.vue
@@ -46,7 +46,7 @@ const state = reactive({
type: 'shadow',
},
formatter: (params: any) => {
- const usageData = params.find((p) => p.seriesName === 'Budget Usage Trend');
+ const usageData = params.find((p) => p.seriesName === 'Actual Spend');
if (!usageData) return '';
const index = usageData.dataIndex;
@@ -73,6 +73,13 @@ const state = reactive({
},
legend: {
show: false,
+ icon: 'circle',
+ data: [
+ { name: 'Planned Budget', itemStyle: { color: indigo[100] } },
+ { name: 'Actual Spend (≤ 90%)', itemStyle: { color: indigo[400] } },
+ { name: 'Actual Spend (90–100%)', itemStyle: { color: yellow[500] } },
+ { name: 'Actual Spend (> 100%)', itemStyle: { color: red[400] } },
+ ],
},
xAxis: {
type: 'category',
@@ -122,7 +129,7 @@ const drawChart = (data: any) => {
state.chartData = [
{
- name: 'Budget Background (0~100%)',
+ name: 'Planned Budget',
type: 'bar',
data: state.data.map(() => 100),
barGap: '-100%',
@@ -132,7 +139,7 @@ const drawChart = (data: any) => {
silent: true,
},
{
- name: 'Budget Usage Trend',
+ name: 'Actual Spend',
type: 'bar',
data: state.data.map((item) => Number(((item.budget_usage / item.budget) * 100).toFixed(2))),
minBarHeight: 4,
@@ -145,8 +152,8 @@ const drawChart = (data: any) => {
itemStyle: {
color: (params: any) => {
const value = Number(params.value);
- if (value >= 100) return red[400];
- if (value >= 90) return yellow[500];
+ if (value > 100) return red[400];
+ if (value > 90) return yellow[500];
return indigo[400];
},
},
@@ -178,10 +185,30 @@ onBeforeUnmount(() => {
-
-
+
+
+
+
+
+
{{ $t('BILLING.COST_MANAGEMENT.BUDGET.DETAIL.BUDGET_USAGE_TREND.PLANNED_BUDGET') }}
+
+
+
+
{{ $t('BILLING.COST_MANAGEMENT.BUDGET.DETAIL.BUDGET_USAGE_TREND.ACTUAL_SPEND') }}
+
+
+
+
90% < {{ $t('BILLING.COST_MANAGEMENT.BUDGET.DETAIL.BUDGET_USAGE_TREND.ACTUAL_SPEND') }} > 100%
+
+
+
+
{{ $t('BILLING.COST_MANAGEMENT.BUDGET.DETAIL.BUDGET_USAGE_TREND.ACTUAL_SPEND') }} > 100%
+
+
diff --git a/apps/web/src/services/cost-explorer/components/BudgetUsageTrendTotalChart.vue b/apps/web/src/services/cost-explorer/components/BudgetUsageTrendTotalChart.vue
index 1790b9f8e3..ad0c627ff8 100644
--- a/apps/web/src/services/cost-explorer/components/BudgetUsageTrendTotalChart.vue
+++ b/apps/web/src/services/cost-explorer/components/BudgetUsageTrendTotalChart.vue
@@ -84,6 +84,7 @@ const state = reactive({
show: true,
left: '40px',
bottom: 0,
+ selectedMode: false,
formatter: (name) => name,
textStyle: {
rich: {
diff --git a/packages/language-pack/console-translation-2.8.babel b/packages/language-pack/console-translation-2.8.babel
index 3d4af776fa..47d87f1e24 100644
--- a/packages/language-pack/console-translation-2.8.babel
+++ b/packages/language-pack/console-translation-2.8.babel
@@ -13114,15 +13114,15 @@
en-US
- false
+ true
ja-JP
- false
+ true
ko-KR
- false
+ true
@@ -13135,15 +13135,15 @@
en-US
- false
+ true
ja-JP
- false
+ true
ko-KR
- false
+ true
diff --git a/packages/language-pack/en.json b/packages/language-pack/en.json
index 108b116a13..2fd0a3e2f7 100644
--- a/packages/language-pack/en.json
+++ b/packages/language-pack/en.json
@@ -690,8 +690,8 @@
"OTHERS": "Others",
"PREVIOUS": "Previous",
"PROJECT": "Project",
- "PROJECT_DUPLICATED_WARNING1": "{project} already has Budget Year of {year_list}",
- "PROJECT_DUPLICATED_WARNING2": "{project} of {serviceAccount} already has Budget Year of {year_list}",
+ "PROJECT_DUPLICATED_WARNING1": "The project {project} already has a budget set for the following month(s): {month_list}",
+ "PROJECT_DUPLICATED_WARNING2": "The service account {serviceAccount} under project {project} already has a budget set for the following month(s): {month_list}",
"PROJECT_MEMBER": "Project Member",
"PROJECT_VALIDATION": "The project is already in use.",
"REQUIRED_NAME": "Please enter name.",
diff --git a/packages/language-pack/ja.json b/packages/language-pack/ja.json
index ac38981e95..9a773ed934 100644
--- a/packages/language-pack/ja.json
+++ b/packages/language-pack/ja.json
@@ -690,8 +690,8 @@
"OTHERS": "その他",
"PREVIOUS": "前へ",
"PROJECT": "プロジェクト",
- "PROJECT_DUPLICATED_WARNING1": "",
- "PROJECT_DUPLICATED_WARNING2": "",
+ "PROJECT_DUPLICATED_WARNING1": "プロジェクト「{project}」には、すでに以下の月の予算が設定されています:{month_list}",
+ "PROJECT_DUPLICATED_WARNING2": "プロジェクト「{project}」のサービスアカウント「{serviceAccount}」には、すでに以下の月の予算が設定されています:{month_list}",
"PROJECT_MEMBER": "プロジェクトメンバー",
"PROJECT_VALIDATION": "",
"REQUIRED_NAME": "名前を入力してください",
diff --git a/packages/language-pack/ko.json b/packages/language-pack/ko.json
index 42a34affaa..15e849d74b 100644
--- a/packages/language-pack/ko.json
+++ b/packages/language-pack/ko.json
@@ -690,8 +690,8 @@
"OTHERS": "이외 사용자",
"PREVIOUS": "뒤로가기",
"PROJECT": "프로젝트",
- "PROJECT_DUPLICATED_WARNING1": "",
- "PROJECT_DUPLICATED_WARNING2": "",
+ "PROJECT_DUPLICATED_WARNING1": " {project} 프로젝트에는 이미 해당 월에 대한 예산이 설정되어 있습니다: {month_list}",
+ "PROJECT_DUPLICATED_WARNING2": "프로젝트 {project} 의 서비스 어카운트 {serviceAccount} 에는 이미 해당 월(들)에 대한 예산이 설정되어 있습니다: {month_list}",
"PROJECT_MEMBER": "프로젝트 멤버",
"PROJECT_VALIDATION": "",
"REQUIRED_NAME": "이름을 입력하세요.",