dfa1680a19
ci / backend (push) Failing after 0s
ci / static (push) Failing after 11s
ci / backend (pull_request) Failing after 1s
ci / static (pull_request) Failing after 12s
Build & Test with Secrets / build (pull_request) Failing after 2s
ci / publish (push) Has been cancelled
ci / frontend (push) Has been cancelled
Build & Test with Secrets / security-scan (pull_request) Has been cancelled
Build & Test with Secrets / notification (pull_request) Has been cancelled
Build & Test with Secrets / frontend (pull_request) Has been cancelled
ci / publish (pull_request) Has been cancelled
ci / frontend (pull_request) Has been cancelled
408 lines
16 KiB
JavaScript
408 lines
16 KiB
JavaScript
import { ref, computed } from 'vue';
|
||
import { useRouter } from 'vue-router';
|
||
const router = useRouter();
|
||
const isLoading = ref(false);
|
||
const jobId = ref(null);
|
||
const form = ref({
|
||
dataSource: 'KRX',
|
||
fromDate: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
|
||
toDate: new Date().toISOString().split('T')[0],
|
||
});
|
||
const minDate = '2015-01-01'; // KRX historical data starts here
|
||
const maxDate = new Date().toISOString().split('T')[0]; // Today
|
||
const validationErrors = computed(() => {
|
||
const errors = [];
|
||
if (!form.value.fromDate)
|
||
errors.push('From Date is required');
|
||
if (!form.value.toDate)
|
||
errors.push('To Date is required');
|
||
if (form.value.fromDate && form.value.toDate) {
|
||
if (form.value.fromDate > form.value.toDate) {
|
||
errors.push('From Date must be before To Date');
|
||
}
|
||
if (form.value.toDate > maxDate) {
|
||
errors.push('To Date cannot be in the future');
|
||
}
|
||
}
|
||
return errors;
|
||
});
|
||
const daysCount = computed(() => {
|
||
if (!form.value.fromDate || !form.value.toDate)
|
||
return 0;
|
||
const from = new Date(form.value.fromDate);
|
||
const to = new Date(form.value.toDate);
|
||
return Math.ceil((to.getTime() - from.getTime()) / (1000 * 60 * 60 * 24));
|
||
});
|
||
const estimatedRows = computed(() => {
|
||
// KRX: ~2000 stocks × days
|
||
// OpenDart: ~200 quarterly filings
|
||
if (form.value.dataSource === 'KRX') {
|
||
return (daysCount.value * 2000).toLocaleString();
|
||
}
|
||
else if (form.value.dataSource === 'OpenDart') {
|
||
return (Math.ceil(daysCount.value / 90) * 200).toLocaleString();
|
||
}
|
||
return '0';
|
||
});
|
||
const setPreset = (preset) => {
|
||
const today = new Date();
|
||
const from = new Date();
|
||
if (preset === '1y')
|
||
from.setFullYear(from.getFullYear() - 1);
|
||
else if (preset === '2y')
|
||
from.setFullYear(from.getFullYear() - 2);
|
||
else if (preset === '5y')
|
||
from.setFullYear(from.getFullYear() - 5);
|
||
else if (preset === 'all')
|
||
from.setFullYear(2015);
|
||
form.value.fromDate = from.toISOString().split('T')[0];
|
||
form.value.toDate = today.toISOString().split('T')[0];
|
||
};
|
||
const triggerIngestion = async () => {
|
||
if (validationErrors.value.length > 0)
|
||
return;
|
||
isLoading.value = true;
|
||
try {
|
||
const response = await fetch('/api/market/ingest', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-KArtSell-User': 'ingestion-user',
|
||
'X-KArtSell-Role': 'DataAdmin',
|
||
},
|
||
body: JSON.stringify({
|
||
dataSource: form.value.dataSource,
|
||
fromDate: form.value.fromDate,
|
||
toDate: form.value.toDate,
|
||
}),
|
||
});
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}`);
|
||
}
|
||
const data = await response.json();
|
||
jobId.value = data.jobId;
|
||
// Reset form after success
|
||
setTimeout(() => {
|
||
form.value.fromDate = new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
||
form.value.toDate = new Date().toISOString().split('T')[0];
|
||
jobId.value = null;
|
||
}, 5000);
|
||
}
|
||
catch (error) {
|
||
console.error('Ingestion error:', error);
|
||
alert(`Failed to trigger ingestion: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||
}
|
||
finally {
|
||
isLoading.value = false;
|
||
}
|
||
};
|
||
const resetForm = () => {
|
||
form.value = {
|
||
dataSource: 'KRX',
|
||
fromDate: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
|
||
toDate: new Date().toISOString().split('T')[0],
|
||
};
|
||
jobId.value = null;
|
||
};
|
||
const __VLS_ctx = {
|
||
...{},
|
||
...{},
|
||
};
|
||
let __VLS_components;
|
||
let __VLS_intrinsics;
|
||
let __VLS_directives;
|
||
/** @type {__VLS_StyleScopedClasses['header']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['config-card']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['summary-card']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['error-card']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['success-card']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['form-group']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['form-group']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['form-group']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['preset-btn']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['summary-item']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['summary-item']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['error-card']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['error-card']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['error-card']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['error-card']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['success-card']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['success-card']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['job-info']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['job-info']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['btn-primary']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['btn-primary']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['btn-primary']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['btn-secondary']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['btn-secondary']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['btn-link']} */ ;
|
||
/** @type {__VLS_StyleScopedClasses['btn-link']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "market-data-ingestion" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['market-data-ingestion']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "header" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['header']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.h1, __VLS_intrinsics.h1)({});
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({
|
||
...{ class: "subtitle" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['subtitle']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "content" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['content']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "config-card" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['config-card']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.h2, __VLS_intrinsics.h2)({});
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "form-group" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['form-group']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.label, __VLS_intrinsics.label)({});
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.select, __VLS_intrinsics.select)({
|
||
value: (__VLS_ctx.form.dataSource),
|
||
});
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.option, __VLS_intrinsics.option)({
|
||
value: "KRX",
|
||
});
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.option, __VLS_intrinsics.option)({
|
||
value: "OpenDart",
|
||
});
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.option, __VLS_intrinsics.option)({
|
||
value: "Stub",
|
||
});
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({
|
||
...{ class: "hint" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['hint']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.strong, __VLS_intrinsics.strong)({});
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.strong, __VLS_intrinsics.strong)({});
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "form-row" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['form-row']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "form-group" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['form-group']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.label, __VLS_intrinsics.label)({});
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.input)({
|
||
type: "date",
|
||
min: (__VLS_ctx.minDate),
|
||
max: (__VLS_ctx.maxDate),
|
||
placeholder: "YYYY-MM-DD",
|
||
});
|
||
(__VLS_ctx.form.fromDate);
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({
|
||
...{ class: "hint" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['hint']} */ ;
|
||
(__VLS_ctx.minDate);
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "form-group" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['form-group']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.label, __VLS_intrinsics.label)({});
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.input)({
|
||
type: "date",
|
||
min: (__VLS_ctx.form.fromDate || __VLS_ctx.minDate),
|
||
max: (__VLS_ctx.maxDate),
|
||
placeholder: "YYYY-MM-DD",
|
||
});
|
||
(__VLS_ctx.form.toDate);
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({
|
||
...{ class: "hint" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['hint']} */ ;
|
||
(__VLS_ctx.maxDate);
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "presets" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['presets']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({
|
||
...{ onClick: (...[$event]) => {
|
||
return (__VLS_ctx.setPreset('1y'));
|
||
// @ts-ignore
|
||
[form, form, form, form, minDate, minDate, minDate, maxDate, maxDate, maxDate, setPreset,];
|
||
} },
|
||
...{ class: "preset-btn" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['preset-btn']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({
|
||
...{ onClick: (...[$event]) => {
|
||
return (__VLS_ctx.setPreset('2y'));
|
||
// @ts-ignore
|
||
[setPreset,];
|
||
} },
|
||
...{ class: "preset-btn" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['preset-btn']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({
|
||
...{ onClick: (...[$event]) => {
|
||
return (__VLS_ctx.setPreset('5y'));
|
||
// @ts-ignore
|
||
[setPreset,];
|
||
} },
|
||
...{ class: "preset-btn" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['preset-btn']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({
|
||
...{ onClick: (...[$event]) => {
|
||
return (__VLS_ctx.setPreset('all'));
|
||
// @ts-ignore
|
||
[setPreset,];
|
||
} },
|
||
...{ class: "preset-btn" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['preset-btn']} */ ;
|
||
if (__VLS_ctx.validationErrors.length) {
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "error-card" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['error-card']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.h3, __VLS_intrinsics.h3)({});
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.ul, __VLS_intrinsics.ul)({});
|
||
for (const [err, idx] of __VLS_vFor((__VLS_ctx.validationErrors))) {
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.li, __VLS_intrinsics.li)({
|
||
key: (idx),
|
||
});
|
||
(err);
|
||
// @ts-ignore
|
||
[validationErrors, validationErrors,];
|
||
}
|
||
}
|
||
if (!__VLS_ctx.validationErrors.length) {
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "summary-card" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['summary-card']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.h3, __VLS_intrinsics.h3)({});
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "summary-grid" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['summary-grid']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "summary-item" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['summary-item']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({
|
||
...{ class: "label" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['label']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({
|
||
...{ class: "value" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['value']} */ ;
|
||
(__VLS_ctx.form.dataSource);
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "summary-item" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['summary-item']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({
|
||
...{ class: "label" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['label']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({
|
||
...{ class: "value" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['value']} */ ;
|
||
(__VLS_ctx.form.fromDate);
|
||
(__VLS_ctx.form.toDate);
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "summary-item" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['summary-item']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({
|
||
...{ class: "label" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['label']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({
|
||
...{ class: "value" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['value']} */ ;
|
||
(__VLS_ctx.daysCount);
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "summary-item" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['summary-item']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({
|
||
...{ class: "label" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['label']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({
|
||
...{ class: "value" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['value']} */ ;
|
||
(__VLS_ctx.estimatedRows);
|
||
}
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "actions" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['actions']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({
|
||
...{ onClick: (__VLS_ctx.triggerIngestion) },
|
||
disabled: (__VLS_ctx.isLoading || __VLS_ctx.validationErrors.length > 0),
|
||
...{ class: "btn-primary" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['btn-primary']} */ ;
|
||
if (!__VLS_ctx.isLoading) {
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({});
|
||
}
|
||
else {
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({});
|
||
}
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({
|
||
...{ onClick: (__VLS_ctx.resetForm) },
|
||
...{ class: "btn-secondary" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['btn-secondary']} */ ;
|
||
if (__VLS_ctx.jobId) {
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "success-card" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['success-card']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.h3, __VLS_intrinsics.h3)({});
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
||
...{ class: "job-info" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['job-info']} */ ;
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({});
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.strong, __VLS_intrinsics.strong)({});
|
||
(__VLS_ctx.jobId);
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({});
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.strong, __VLS_intrinsics.strong)({});
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({});
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.strong, __VLS_intrinsics.strong)({});
|
||
(new Date().toLocaleString());
|
||
__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({
|
||
...{ class: "hint" },
|
||
});
|
||
/** @type {__VLS_StyleScopedClasses['hint']} */ ;
|
||
let __VLS_0;
|
||
/** @ts-ignore @type { | typeof __VLS_components.routerLink | typeof __VLS_components.RouterLink | typeof __VLS_components['router-link'] | typeof __VLS_components.routerLink | typeof __VLS_components.RouterLink | typeof __VLS_components['router-link']} */
|
||
routerLink;
|
||
// @ts-ignore
|
||
const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({
|
||
to: "/ops/market-data-history",
|
||
...{ class: "btn-link" },
|
||
}));
|
||
const __VLS_2 = __VLS_1({
|
||
to: "/ops/market-data-history",
|
||
...{ class: "btn-link" },
|
||
}, ...__VLS_functionalComponentArgsRest(__VLS_1));
|
||
/** @type {__VLS_StyleScopedClasses['btn-link']} */ ;
|
||
const { default: __VLS_5 } = __VLS_3.slots;
|
||
// @ts-ignore
|
||
[form, form, form, validationErrors, validationErrors, daysCount, estimatedRows, triggerIngestion, isLoading, isLoading, resetForm, jobId, jobId,];
|
||
var __VLS_3;
|
||
}
|
||
// @ts-ignore
|
||
[];
|
||
const __VLS_export = (await import('vue')).defineComponent({});
|
||
export default {};
|