diff --git a/db/migrations/0015_signal_engine_semantic_versioning.sql b/db/migrations/0015_signal_engine_semantic_versioning.sql index 7f81b7bc..f47d93df 100644 --- a/db/migrations/0015_signal_engine_semantic_versioning.sql +++ b/db/migrations/0015_signal_engine_semantic_versioning.sql @@ -56,7 +56,7 @@ create table if not exists signal_engine.policy_contract_definition ( insert into signal_engine.policy_contract_definition (contract_version, content_hash, policy_json, status) values -('sell-policy.v1', 'a269a0331b83c0f6ec108e7587de1d20c798036ff8d0c03d726cd854e73d8480', $policy$ +('sell-policy.v1', 'a269a0331b83c0f6ec108e7587de1d20c798036ff8d0c03d726cd854e73d8480', $$ { "changeControl": "MODEL_CHANGE_AND_GOLDEN_OOS_REQUIRED", "contractVersion": "sell-policy.v1", @@ -140,7 +140,7 @@ values "policyTraceSchemaVersion": 2, "status": "RESEARCH_CANDIDATE_NOT_PRODUCTION" } -$policy$::jsonb, 'PROPOSED') +$$::jsonb, 'PROPOSED') on conflict (contract_version) do nothing; drop trigger if exists policy_contract_definition_immutable on signal_engine.policy_contract_definition; diff --git a/frontend/src/App.vue.js b/frontend/src/App.vue.js new file mode 100644 index 00000000..f2945625 --- /dev/null +++ b/frontend/src/App.vue.js @@ -0,0 +1,83 @@ +import { RouterLink, RouterView } from 'vue-router'; +import { AppShellLayout } from './shared/ui/layouts'; +const __VLS_ctx = { + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +/** @type {__VLS_StyleScopedClasses['app-nav']} */ ; +/** @type {__VLS_StyleScopedClasses['app-nav']} */ ; +let __VLS_0; +/** @ts-ignore @type { | typeof __VLS_components.AppShellLayout | typeof __VLS_components.AppShellLayout} */ +AppShellLayout; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({})); +const __VLS_2 = __VLS_1({}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +{ + const { navigation: __VLS_7 } = __VLS_3.slots; + __VLS_asFunctionalElement1(__VLS_intrinsics.nav, __VLS_intrinsics.nav)({ + ...{ class: "app-nav" }, + }); + /** @type {__VLS_StyleScopedClasses['app-nav']} */ ; + let __VLS_8; + /** @ts-ignore @type { | typeof __VLS_components.RouterLink | typeof __VLS_components.RouterLink} */ + RouterLink; + // @ts-ignore + const __VLS_9 = __VLS_asFunctionalComponent1(__VLS_8, new __VLS_8({ + to: "/research/sell-decision", + })); + const __VLS_10 = __VLS_9({ + to: "/research/sell-decision", + }, ...__VLS_functionalComponentArgsRest(__VLS_9)); + const { default: __VLS_13 } = __VLS_11.slots; + var __VLS_11; + let __VLS_14; + /** @ts-ignore @type { | typeof __VLS_components.RouterLink | typeof __VLS_components.RouterLink} */ + RouterLink; + // @ts-ignore + const __VLS_15 = __VLS_asFunctionalComponent1(__VLS_14, new __VLS_14({ + to: "/ops/data-quality", + })); + const __VLS_16 = __VLS_15({ + to: "/ops/data-quality", + }, ...__VLS_functionalComponentArgsRest(__VLS_15)); + const { default: __VLS_19 } = __VLS_17.slots; + var __VLS_17; + let __VLS_20; + /** @ts-ignore @type { | typeof __VLS_components.RouterLink | typeof __VLS_components.RouterLink} */ + RouterLink; + // @ts-ignore + const __VLS_21 = __VLS_asFunctionalComponent1(__VLS_20, new __VLS_20({ + to: "/ops/model-operations", + })); + const __VLS_22 = __VLS_21({ + to: "/ops/model-operations", + }, ...__VLS_functionalComponentArgsRest(__VLS_21)); + const { default: __VLS_25 } = __VLS_23.slots; + var __VLS_23; + let __VLS_26; + /** @ts-ignore @type { | typeof __VLS_components.RouterLink | typeof __VLS_components.RouterLink} */ + RouterLink; + // @ts-ignore + const __VLS_27 = __VLS_asFunctionalComponent1(__VLS_26, new __VLS_26({ + to: "/internal/ui-standard", + })); + const __VLS_28 = __VLS_27({ + to: "/internal/ui-standard", + }, ...__VLS_functionalComponentArgsRest(__VLS_27)); + const { default: __VLS_31 } = __VLS_29.slots; + var __VLS_29; +} +let __VLS_32; +/** @ts-ignore @type { | typeof __VLS_components.RouterView} */ +RouterView; +// @ts-ignore +const __VLS_33 = __VLS_asFunctionalComponent1(__VLS_32, new __VLS_32({})); +const __VLS_34 = __VLS_33({}, ...__VLS_functionalComponentArgsRest(__VLS_33)); +var __VLS_3; +const __VLS_export = (await import('vue')).defineComponent({}); +export default {}; diff --git a/frontend/src/app/queryClient.js b/frontend/src/app/queryClient.js new file mode 100644 index 00000000..fa7104e5 --- /dev/null +++ b/frontend/src/app/queryClient.js @@ -0,0 +1,16 @@ +import { QueryClient } from '@tanstack/vue-query'; +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30_000, + retry: (failureCount, error) => { + const status = typeof error === 'object' && error !== null && 'status' in error + ? Number(error.status) + : 0; + return ![400, 401, 403, 404, 409, 422].includes(status) && failureCount < 2; + }, + refetchOnWindowFocus: false + }, + mutations: { retry: false } + } +}); diff --git a/frontend/src/app/router.js b/frontend/src/app/router.js new file mode 100644 index 00000000..646c10c5 --- /dev/null +++ b/frontend/src/app/router.js @@ -0,0 +1,15 @@ +import { createRouter, createWebHistory } from 'vue-router'; +import SellDecisionPage from '../features/sell-decision/pages/SellDecisionPage.vue'; +import DataQualityPage from '../features/data-quality/pages/DataQualityPage.vue'; +import ModelOperationsPage from '../features/model-operations/pages/ModelOperationsPage.vue'; +import UiStandardPage from '../features/ui-standard/pages/UiStandardPage.vue'; +export const router = createRouter({ + history: createWebHistory(), + routes: [ + { path: '/', redirect: '/research/sell-decision' }, + { path: '/research/sell-decision', component: SellDecisionPage, meta: { screenId: 'SCR-002', templateId: 'T02' } }, + { path: '/ops/data-quality', component: DataQualityPage, meta: { screenId: 'SCR-013', templateId: 'T08' } }, + { path: '/ops/model-operations', component: ModelOperationsPage, meta: { screenId: 'SCR-015', templateId: 'T10' } }, + { path: '/internal/ui-standard', component: UiStandardPage, meta: { screenId: 'SCR-DEV-001', templateId: 'T01', internalOnly: true } } + ] +}); diff --git a/frontend/src/features/data-quality/pages/DataQualityPage.vue.js b/frontend/src/features/data-quality/pages/DataQualityPage.vue.js new file mode 100644 index 00000000..efa8fbfa --- /dev/null +++ b/frontend/src/features/data-quality/pages/DataQualityPage.vue.js @@ -0,0 +1,41 @@ +import { computed } from 'vue'; +import DataGridShell from '../../../shared/ui/DataGridShell.vue'; +// Template fixture only. Production data must come from DAT-03 and pass Zod validation. +const rows = []; +const columns = computed(() => [ + { field: 'source', header: 'Source' }, + { field: 'session', header: 'Session' }, + { field: 'status', header: 'DQ' }, + { field: 'rowCount', header: 'Rows' }, + { field: 'failedRows', header: 'Failed' }, + { field: 'sourceWatermark', header: 'Watermark' }, + { field: 'datasetId', header: 'Dataset' }, + { field: 'completedAt', header: 'Completed' } +]); +const __VLS_ctx = { + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.main, __VLS_intrinsics.main)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.header, __VLS_intrinsics.header)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.h1, __VLS_intrinsics.h1)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({}); +const __VLS_0 = DataGridShell; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + rows: (__VLS_ctx.rows), + columns: (__VLS_ctx.columns), + emptyMessage: "DAT-03 계약이 구현되면 서버 검증 결과가 표시됩니다.", +})); +const __VLS_2 = __VLS_1({ + rows: (__VLS_ctx.rows), + columns: (__VLS_ctx.columns), + emptyMessage: "DAT-03 계약이 구현되면 서버 검증 결과가 표시됩니다.", +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +// @ts-ignore +[rows, columns,]; +const __VLS_export = (await import('vue')).defineComponent({}); +export default {}; diff --git a/frontend/src/features/data-quality/schema.js b/frontend/src/features/data-quality/schema.js new file mode 100644 index 00000000..a7b9e654 --- /dev/null +++ b/frontend/src/features/data-quality/schema.js @@ -0,0 +1,23 @@ +import { z } from 'zod'; +export const dataQualityStatusSchema = z.enum(['PASS', 'WARN', 'QUARANTINED']); +export const dataQualityRunSchema = z.object({ + runId: z.string().uuid(), + source: z.string().min(1), + session: z.string().min(1), + status: dataQualityStatusSchema, + rowCount: z.number().int().nonnegative(), + failedRows: z.number().int().nonnegative(), + sourceWatermark: z.string().min(1), + datasetId: z.string().min(1), + contentHash: z.string().min(1), + completedAt: z.string().datetime({ offset: true }) +}).superRefine((value, ctx) => { + if (value.failedRows > value.rowCount) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: '실패 행 수는 전체 행 수를 초과할 수 없습니다.', + path: ['failedRows'] + }); + } +}); +export const dataQualityRunsSchema = z.array(dataQualityRunSchema); diff --git a/frontend/src/features/data-quality/tests/schema.spec.js b/frontend/src/features/data-quality/tests/schema.spec.js new file mode 100644 index 00000000..45a0ff01 --- /dev/null +++ b/frontend/src/features/data-quality/tests/schema.spec.js @@ -0,0 +1,22 @@ +import { describe, expect, it } from 'vitest'; +import { dataQualityRunSchema } from '../schema'; +const valid = { + runId: '00000000-0000-0000-0000-000000000001', + source: 'KRX', + session: '2026-08-01', + status: 'PASS', + rowCount: 100, + failedRows: 0, + sourceWatermark: 'KRX:2026-08-01', + datasetId: 'dataset-1', + contentHash: 'hash-1', + completedAt: '2026-08-01T09:00:00Z' +}; +describe('data quality contract', () => { + it('accepts a valid run', () => { + expect(dataQualityRunSchema.safeParse(valid).success).toBe(true); + }); + it('rejects failed rows greater than total rows', () => { + expect(dataQualityRunSchema.safeParse({ ...valid, failedRows: 101 }).success).toBe(false); + }); +}); diff --git a/frontend/src/features/model-operations/api.js b/frontend/src/features/model-operations/api.js new file mode 100644 index 00000000..8ef7a1c0 --- /dev/null +++ b/frontend/src/features/model-operations/api.js @@ -0,0 +1,6 @@ +import { api } from '../../shared/api/client'; +import { modelOperationsPlanSchema } from './schema'; +export async function getModelOperationsPlan() { + const response = await api.get('/internal/v1/model-operations/plan'); + return modelOperationsPlanSchema.parse(response.data); +} diff --git a/frontend/src/features/model-operations/components/AutomationBoundaryPanel.vue.js b/frontend/src/features/model-operations/components/AutomationBoundaryPanel.vue.js new file mode 100644 index 00000000..a2a4e895 --- /dev/null +++ b/frontend/src/features/model-operations/components/AutomationBoundaryPanel.vue.js @@ -0,0 +1,35 @@ +const __VLS_props = defineProps(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + 'aria-labelledby': "automation-boundary-title", +}); +__VLS_asFunctionalElement1(__VLS_intrinsics.h2, __VLS_intrinsics.h2)({ + id: "automation-boundary-title", +}); +__VLS_asFunctionalElement1(__VLS_intrinsics.dl, __VLS_intrinsics.dl)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); +(__VLS_ctx.algorithmStatus); +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); +(__VLS_ctx.orderCapability); +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); +(__VLS_ctx.modelMutationBoundary); +__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({}); +// @ts-ignore +[algorithmStatus, orderCapability, modelMutationBoundary,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/features/model-operations/components/ModelOperationTable.vue.js b/frontend/src/features/model-operations/components/ModelOperationTable.vue.js new file mode 100644 index 00000000..c19cc1af --- /dev/null +++ b/frontend/src/features/model-operations/components/ModelOperationTable.vue.js @@ -0,0 +1,61 @@ +const __VLS_props = defineProps(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + 'aria-labelledby': "operation-plan-title", +}); +__VLS_asFunctionalElement1(__VLS_intrinsics.h2, __VLS_intrinsics.h2)({ + id: "operation-plan-title", +}); +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "table-wrap" }, +}); +/** @type {__VLS_StyleScopedClasses['table-wrap']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.table, __VLS_intrinsics.table)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.thead, __VLS_intrinsics.thead)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.tr, __VLS_intrinsics.tr)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.th, __VLS_intrinsics.th)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.th, __VLS_intrinsics.th)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.th, __VLS_intrinsics.th)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.th, __VLS_intrinsics.th)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.th, __VLS_intrinsics.th)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.th, __VLS_intrinsics.th)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.th, __VLS_intrinsics.th)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.th, __VLS_intrinsics.th)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.tbody, __VLS_intrinsics.tbody)({}); +for (const [operation] of __VLS_vFor((__VLS_ctx.operations))) { + __VLS_asFunctionalElement1(__VLS_intrinsics.tr, __VLS_intrinsics.tr)({ + key: (operation.operationCode), + }); + __VLS_asFunctionalElement1(__VLS_intrinsics.td, __VLS_intrinsics.td)({}); + (operation.operationCode); + __VLS_asFunctionalElement1(__VLS_intrinsics.td, __VLS_intrinsics.td)({}); + (operation.name); + __VLS_asFunctionalElement1(__VLS_intrinsics.td, __VLS_intrinsics.td)({}); + (operation.cadence); + __VLS_asFunctionalElement1(__VLS_intrinsics.td, __VLS_intrinsics.td)({}); + (operation.automationMode); + __VLS_asFunctionalElement1(__VLS_intrinsics.td, __VLS_intrinsics.td)({}); + (operation.queue); + __VLS_asFunctionalElement1(__VLS_intrinsics.td, __VLS_intrinsics.td)({}); + (operation.gate); + __VLS_asFunctionalElement1(__VLS_intrinsics.td, __VLS_intrinsics.td)({}); + (operation.primaryOwner); + (operation.secondaryOwner); + __VLS_asFunctionalElement1(__VLS_intrinsics.td, __VLS_intrinsics.td)({}); + (operation.output); + // @ts-ignore + [operations,]; +} +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/features/model-operations/pages/ModelOperationsPage.vue.js b/frontend/src/features/model-operations/pages/ModelOperationsPage.vue.js new file mode 100644 index 00000000..73c2a4ce --- /dev/null +++ b/frontend/src/features/model-operations/pages/ModelOperationsPage.vue.js @@ -0,0 +1,58 @@ +import QueryStateBoundary from '../../../shared/ui/QueryStateBoundary.vue'; +import AutomationBoundaryPanel from '../components/AutomationBoundaryPanel.vue'; +import ModelOperationTable from '../components/ModelOperationTable.vue'; +import { useModelOperationsPlanQuery } from '../queries'; +const planQuery = useModelOperationsPlanQuery(); +const __VLS_ctx = { + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.article, __VLS_intrinsics.article)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.header, __VLS_intrinsics.header)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.h1, __VLS_intrinsics.h1)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({}); +const __VLS_0 = QueryStateBoundary || QueryStateBoundary; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + loading: (__VLS_ctx.planQuery.isLoading.value), + error: __VLS_ctx.planQuery.error.value, + empty: (!__VLS_ctx.planQuery.data.value), +})); +const __VLS_2 = __VLS_1({ + loading: (__VLS_ctx.planQuery.isLoading.value), + error: __VLS_ctx.planQuery.error.value, + empty: (!__VLS_ctx.planQuery.data.value), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +const { default: __VLS_5 } = __VLS_3.slots; +if (__VLS_ctx.planQuery.data.value) { + const __VLS_6 = AutomationBoundaryPanel; + // @ts-ignore + const __VLS_7 = __VLS_asFunctionalComponent1(__VLS_6, new __VLS_6({ + algorithmStatus: (__VLS_ctx.planQuery.data.value.algorithmStatus), + orderCapability: (__VLS_ctx.planQuery.data.value.orderCapability), + modelMutationBoundary: (__VLS_ctx.planQuery.data.value.modelMutationBoundary), + })); + const __VLS_8 = __VLS_7({ + algorithmStatus: (__VLS_ctx.planQuery.data.value.algorithmStatus), + orderCapability: (__VLS_ctx.planQuery.data.value.orderCapability), + modelMutationBoundary: (__VLS_ctx.planQuery.data.value.modelMutationBoundary), + }, ...__VLS_functionalComponentArgsRest(__VLS_7)); + const __VLS_11 = ModelOperationTable; + // @ts-ignore + const __VLS_12 = __VLS_asFunctionalComponent1(__VLS_11, new __VLS_11({ + operations: (__VLS_ctx.planQuery.data.value.operations), + })); + const __VLS_13 = __VLS_12({ + operations: (__VLS_ctx.planQuery.data.value.operations), + }, ...__VLS_functionalComponentArgsRest(__VLS_12)); +} +// @ts-ignore +[planQuery, planQuery, planQuery, planQuery, planQuery, planQuery, planQuery, planQuery,]; +var __VLS_3; +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({}); +export default {}; diff --git a/frontend/src/features/model-operations/queries.js b/frontend/src/features/model-operations/queries.js new file mode 100644 index 00000000..36bc2593 --- /dev/null +++ b/frontend/src/features/model-operations/queries.js @@ -0,0 +1,14 @@ +import { useQuery } from '@tanstack/vue-query'; +import { getModelOperationsPlan } from './api'; +export const modelOperationsKeys = { + all: ['model-operations'], + plan: () => [...modelOperationsKeys.all, 'plan'] +}; +export function useModelOperationsPlanQuery() { + return useQuery({ + queryKey: modelOperationsKeys.plan(), + queryFn: getModelOperationsPlan, + staleTime: 5 * 60 * 1000, + retry: 1 + }); +} diff --git a/frontend/src/features/model-operations/schema.js b/frontend/src/features/model-operations/schema.js new file mode 100644 index 00000000..89915ff6 --- /dev/null +++ b/frontend/src/features/model-operations/schema.js @@ -0,0 +1,19 @@ +import { z } from 'zod'; +export const operationItemSchema = z.object({ + operationCode: z.string().min(1), + name: z.string().min(1), + cadence: z.enum(['DAILY', 'WEEKLY', 'MONTHLY', 'QUARTERLY', 'EVENT_DRIVEN']), + automationMode: z.enum(['EVALUATION_ONLY', 'PROPOSAL_ONLY', 'DRILL_ONLY']), + queue: z.string().min(1), + primaryOwner: z.string().min(1), + secondaryOwner: z.string().min(1), + requiredEvidence: z.string().min(1), + output: z.string().min(1), + gate: z.string().min(1) +}); +export const modelOperationsPlanSchema = z.object({ + algorithmStatus: z.literal('RESEARCH_CANDIDATE_NOT_PRODUCTION'), + orderCapability: z.literal('AUTOMATIC_ORDER_AND_KIS_SUBMISSION_OFF'), + modelMutationBoundary: z.literal('EVALUATION_AND_PROPOSAL_ONLY_HUMAN_APPROVAL_REQUIRED'), + operations: z.array(operationItemSchema) +}); diff --git a/frontend/src/features/model-operations/tests/schema.spec.js b/frontend/src/features/model-operations/tests/schema.spec.js new file mode 100644 index 00000000..df2443ef --- /dev/null +++ b/frontend/src/features/model-operations/tests/schema.spec.js @@ -0,0 +1,24 @@ +import { describe, expect, it } from 'vitest'; +import { modelOperationsPlanSchema } from '../schema'; +describe('model operations plan schema', () => { + it('rejects an automatic promotion mode', () => { + const result = modelOperationsPlanSchema.safeParse({ + algorithmStatus: 'RESEARCH_CANDIDATE_NOT_PRODUCTION', + orderCapability: 'AUTOMATIC_ORDER_AND_KIS_SUBMISSION_OFF', + modelMutationBoundary: 'EVALUATION_AND_PROPOSAL_ONLY_HUMAN_APPROVAL_REQUIRED', + operations: [{ + operationCode: 'J22', + name: 'PromotionEvidenceReviewBuild', + cadence: 'MONTHLY', + automationMode: 'AUTO_PROMOTE', + queue: 'q-control', + primaryOwner: 'Risk', + secondaryOwner: 'Compliance', + requiredEvidence: 'all evidence', + output: 'review packet', + gate: 'G4' + }] + }); + expect(result.success).toBe(false); + }); +}); diff --git a/frontend/src/features/sell-decision/api.js b/frontend/src/features/sell-decision/api.js new file mode 100644 index 00000000..2f956d19 --- /dev/null +++ b/frontend/src/features/sell-decision/api.js @@ -0,0 +1,7 @@ +import { api } from '../../shared/api/client'; +import { researchSellPolicyRequestSchema, researchSellPolicyResponseSchema } from './schema'; +export async function evaluateResearchSellPolicy(command) { + const request = researchSellPolicyRequestSchema.parse(command.request); + const { data } = await api.post('/internal/v1/research/sell-policy/evaluate', request, { headers: { 'Idempotency-Key': command.idempotencyKey } }); + return researchSellPolicyResponseSchema.parse(data); +} diff --git a/frontend/src/features/sell-decision/components/PolicyTracePanel.vue.js b/frontend/src/features/sell-decision/components/PolicyTracePanel.vue.js new file mode 100644 index 00000000..46e43301 --- /dev/null +++ b/frontend/src/features/sell-decision/components/PolicyTracePanel.vue.js @@ -0,0 +1,48 @@ +import { computed } from 'vue'; +const props = defineProps(); +const dispositionLabel = { 0: 'NOT_APPLICABLE', 1: 'BLOCKED', 2: 'APPLIED' }; +const ordered = computed(() => [...props.entries].sort((a, b) => b.priority - a.priority)); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + 'aria-labelledby': "policy-trace-title", +}); +__VLS_asFunctionalElement1(__VLS_intrinsics.h2, __VLS_intrinsics.h2)({ + id: "policy-trace-title", +}); +__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({}); +(props.schemaVersion); +__VLS_asFunctionalElement1(__VLS_intrinsics.ol, __VLS_intrinsics.ol)({}); +for (const [entry] of __VLS_vFor((__VLS_ctx.ordered))) { + __VLS_asFunctionalElement1(__VLS_intrinsics.li, __VLS_intrinsics.li)({ + key: (`${entry.priority}-${entry.policyId}`), + }); + __VLS_asFunctionalElement1(__VLS_intrinsics.strong, __VLS_intrinsics.strong)({}); + (entry.policyId); + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({}); + (__VLS_ctx.dispositionLabel[entry.disposition]); + (entry.reasonCode); + if (entry.requestedSellRatioOfLot > 0) { + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({}); + (entry.requestedSellRatioOfLot); + (entry.appliedSellRatioOfLot); + } + if (entry.strategicCoreClampApplied) { + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({}); + } + // @ts-ignore + [ordered, dispositionLabel,]; +} +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/features/sell-decision/pages/SellDecisionPage.vue.js b/frontend/src/features/sell-decision/pages/SellDecisionPage.vue.js new file mode 100644 index 00000000..d21693c8 --- /dev/null +++ b/frontend/src/features/sell-decision/pages/SellDecisionPage.vue.js @@ -0,0 +1,162 @@ +import { computed, ref } from 'vue'; +import QueryStateBoundary from '../../../shared/ui/QueryStateBoundary.vue'; +import PolicyTracePanel from '../components/PolicyTracePanel.vue'; +import { useEvaluateResearchSellPolicy } from '../queries'; +const mutation = useEvaluateResearchSellPolicy(); +const hardImpairmentApproved = ref(false); +const capitalFloorBreached = ref(false); +const gapBelowFloorAtr = ref(1.6); +const consecutiveCloseBreaches = ref(0); +const lastCommand = ref(null); +const isBusy = computed(() => mutation.isPending.value); +function createCommand() { + const asOf = new Date().toISOString(); + return { + idempotencyKey: crypto.randomUUID(), + request: { + positionLotId: '00000000-0000-0000-0000-000000000001', + cycleId: '00000000-0000-0000-0000-000000000002', + evidenceId: 'sample-evidence', + datasetId: 'sample-dataset', + modelVersion: 'research-v12.2', + configVersion: 'proposal-v12.2', + codeSha: 'sample-code-sha', + asOf, + publishedAtCutoff: asOf, + currentSecurityPortfolioWeight: 0.6, + currentLotPortfolioWeight: 0.2, + strategicCoreFloorWeight: 0.3, + hardImpairmentApproved: hardImpairmentApproved.value, + capitalFloorBreached: capitalFloorBreached.value, + survivalSellRatioOfLot: 0.5, + gapBelowFloorAtr: gapBelowFloorAtr.value, + consecutiveCloseBreaches: consecutiveCloseBreaches.value, + cooldownSatisfied: true, + concentrationSellRatioOfLot: 0, + opportunityEdgeLowerBound: 0, + opportunitySellRatioOfLot: 0 + } + }; +} +function run() { + const command = createCommand(); + lastCommand.value = command; + mutation.mutate(command); +} +function retry() { + if (lastCommand.value) + mutation.mutate(lastCommand.value); +} +const __VLS_ctx = { + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.main, __VLS_intrinsics.main)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.header, __VLS_intrinsics.header)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.h1, __VLS_intrinsics.h1)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.strong, __VLS_intrinsics.strong)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.form, __VLS_intrinsics.form)({ + ...{ onSubmit: (__VLS_ctx.run) }, +}); +__VLS_asFunctionalElement1(__VLS_intrinsics.fieldset, __VLS_intrinsics.fieldset)({ + disabled: (__VLS_ctx.isBusy), +}); +__VLS_asFunctionalElement1(__VLS_intrinsics.legend, __VLS_intrinsics.legend)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.label, __VLS_intrinsics.label)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.input)({ + type: "checkbox", +}); +(__VLS_ctx.hardImpairmentApproved); +__VLS_asFunctionalElement1(__VLS_intrinsics.label, __VLS_intrinsics.label)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.input)({ + type: "checkbox", +}); +(__VLS_ctx.capitalFloorBreached); +__VLS_asFunctionalElement1(__VLS_intrinsics.label, __VLS_intrinsics.label)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.input)({ + type: "number", + min: "0", + step: "0.1", +}); +(__VLS_ctx.gapBelowFloorAtr); +__VLS_asFunctionalElement1(__VLS_intrinsics.label, __VLS_intrinsics.label)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.input)({ + type: "number", + min: "0", + step: "1", +}); +(__VLS_ctx.consecutiveCloseBreaches); +__VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({ + type: "submit", +}); +const __VLS_0 = QueryStateBoundary || QueryStateBoundary; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onRetry': {} }, + loading: (__VLS_ctx.isBusy), + error: __VLS_ctx.mutation.error.value, + empty: (!__VLS_ctx.mutation.data.value), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onRetry': {} }, + loading: (__VLS_ctx.isBusy), + error: __VLS_ctx.mutation.error.value, + empty: (!__VLS_ctx.mutation.data.value), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.retry} */ + onRetry: (__VLS_ctx.retry), +}; +const { default: __VLS_7 } = __VLS_3.slots; +if (__VLS_ctx.mutation.data.value) { + __VLS_asFunctionalElement1(__VLS_intrinsics.dl, __VLS_intrinsics.dl)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); + (__VLS_ctx.mutation.data.value.action); + __VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); + (__VLS_ctx.mutation.data.value.policyId); + __VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); + (__VLS_ctx.mutation.data.value.reasonCode); + __VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); + (__VLS_ctx.mutation.data.value.sellRatioOfLot); + __VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); + (__VLS_ctx.mutation.data.value.targetSecurityPortfolioWeightAfter); + __VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); + (__VLS_ctx.mutation.data.value.reentryEligible); + __VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); + (__VLS_ctx.mutation.data.value.decisionContractVersion); + __VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); + (__VLS_ctx.mutation.data.value.policyTrace.length); +} +if (__VLS_ctx.mutation.data.value) { + const __VLS_8 = PolicyTracePanel; + // @ts-ignore + const __VLS_9 = __VLS_asFunctionalComponent1(__VLS_8, new __VLS_8({ + entries: (__VLS_ctx.mutation.data.value.policyTrace), + schemaVersion: (__VLS_ctx.mutation.data.value.policyTraceSchemaVersion), + })); + const __VLS_10 = __VLS_9({ + entries: (__VLS_ctx.mutation.data.value.policyTrace), + schemaVersion: (__VLS_ctx.mutation.data.value.policyTraceSchemaVersion), + }, ...__VLS_functionalComponentArgsRest(__VLS_9)); +} +// @ts-ignore +[run, isBusy, isBusy, hardImpairmentApproved, capitalFloorBreached, gapBelowFloorAtr, consecutiveCloseBreaches, mutation, mutation, mutation, mutation, mutation, mutation, mutation, mutation, mutation, mutation, mutation, mutation, mutation, mutation, retry,]; +var __VLS_3; +var __VLS_4; +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({}); +export default {}; diff --git a/frontend/src/features/sell-decision/queries.js b/frontend/src/features/sell-decision/queries.js new file mode 100644 index 00000000..9a253063 --- /dev/null +++ b/frontend/src/features/sell-decision/queries.js @@ -0,0 +1,5 @@ +import { useMutation } from '@tanstack/vue-query'; +import { evaluateResearchSellPolicy } from './api'; +export function useEvaluateResearchSellPolicy() { + return useMutation({ mutationFn: evaluateResearchSellPolicy }); +} diff --git a/frontend/src/features/sell-decision/schema.js b/frontend/src/features/sell-decision/schema.js new file mode 100644 index 00000000..528c4e49 --- /dev/null +++ b/frontend/src/features/sell-decision/schema.js @@ -0,0 +1,61 @@ +import { z } from 'zod'; +const ratio = z.number().min(0).max(1); +const policyTraceEntrySchema = z.object({ + policyId: z.string().min(1), + priority: z.number().int(), + disposition: z.union([z.literal(0), z.literal(1), z.literal(2)]), + reasonCode: z.string().min(1), + requestedSellRatioOfLot: ratio, + appliedSellRatioOfLot: ratio, + strategicCoreClampApplied: z.boolean() +}); +export const researchSellPolicyRequestSchema = z.object({ + positionLotId: z.string().uuid(), + cycleId: z.string().uuid(), + evidenceId: z.string().min(1).max(128), + datasetId: z.string().min(1).max(128), + modelVersion: z.string().min(1).max(128), + configVersion: z.string().min(1).max(128), + codeSha: z.string().min(1).max(128), + asOf: z.string().datetime({ offset: true }), + publishedAtCutoff: z.string().datetime({ offset: true }), + currentSecurityPortfolioWeight: ratio, + currentLotPortfolioWeight: ratio, + strategicCoreFloorWeight: ratio, + hardImpairmentApproved: z.boolean(), + capitalFloorBreached: z.boolean(), + survivalSellRatioOfLot: ratio, + gapBelowFloorAtr: z.number().min(0), + consecutiveCloseBreaches: z.number().int().min(0), + cooldownSatisfied: z.boolean(), + concentrationSellRatioOfLot: ratio, + opportunityEdgeLowerBound: z.number(), + opportunitySellRatioOfLot: ratio +}).superRefine((value, ctx) => { + if (value.currentLotPortfolioWeight > value.currentSecurityPortfolioWeight) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Lot 비중은 종목 전체 비중을 초과할 수 없습니다.', + path: ['currentLotPortfolioWeight'] + }); + } + if (new Date(value.publishedAtCutoff) > new Date(value.asOf)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: '공개 가능 시각은 평가 시각 이후일 수 없습니다.', + path: ['publishedAtCutoff'] + }); + } +}); +export const researchSellPolicyResponseSchema = z.object({ + action: z.enum(['Hold', 'PartialSell', 'FullSell']), + sellRatioOfLot: ratio, + targetSecurityPortfolioWeightAfter: ratio, + policyId: z.string().min(1), + reasonCode: z.string().min(1), + decisionContractVersion: z.literal('sell-decision.v2'), + policyTraceSchemaVersion: z.literal(2), + reentryEligible: z.boolean(), + policyTrace: z.array(policyTraceEntrySchema), + evidenceStatus: z.literal('RESEARCH_CANDIDATE_NOT_PRODUCTION') +}); diff --git a/frontend/src/features/sell-decision/tests/schema.spec.js b/frontend/src/features/sell-decision/tests/schema.spec.js new file mode 100644 index 00000000..6ae92df2 --- /dev/null +++ b/frontend/src/features/sell-decision/tests/schema.spec.js @@ -0,0 +1,57 @@ +import { describe, expect, it } from 'vitest'; +import { researchSellPolicyRequestSchema, researchSellPolicyResponseSchema } from '../schema'; +const baseRequest = { + positionLotId: '00000000-0000-0000-0000-000000000001', + cycleId: '00000000-0000-0000-0000-000000000002', + evidenceId: 'evidence-1', + datasetId: 'dataset-1', + modelVersion: 'model-1', + configVersion: 'config-1', + codeSha: 'sha-1', + asOf: '2026-08-01T07:00:00Z', + publishedAtCutoff: '2026-08-01T06:00:00Z', + currentSecurityPortfolioWeight: 0.6, + currentLotPortfolioWeight: 0.2, + strategicCoreFloorWeight: 0.3, + hardImpairmentApproved: false, + capitalFloorBreached: false, + survivalSellRatioOfLot: 0, + gapBelowFloorAtr: 0, + consecutiveCloseBreaches: 0, + cooldownSatisfied: true, + concentrationSellRatioOfLot: 0, + opportunityEdgeLowerBound: 0, + opportunitySellRatioOfLot: 0 +}; +describe('research sell policy contracts', () => { + it('accepts a valid point-in-time request', () => { + expect(researchSellPolicyRequestSchema.safeParse(baseRequest).success).toBe(true); + }); + it('rejects a lot weight above security weight', () => { + const result = researchSellPolicyRequestSchema.safeParse({ + ...baseRequest, + currentLotPortfolioWeight: 0.7 + }); + expect(result.success).toBe(false); + }); + it('rejects a future published-at cutoff', () => { + const result = researchSellPolicyRequestSchema.safeParse({ + ...baseRequest, + publishedAtCutoff: '2026-08-01T08:00:00Z' + }); + expect(result.success).toBe(false); + }); + it('rejects an unknown production status', () => { + const result = researchSellPolicyResponseSchema.safeParse({ + action: 'Hold', + sellRatioOfLot: 0, + targetSecurityPortfolioWeightAfter: 0.6, + policyId: 'ALG-HOLD-001', + reasonCode: 'NO_SELL_CONDITION', + reentryEligible: false, + policyTrace: [], + evidenceStatus: 'PRODUCTION' + }); + expect(result.success).toBe(false); + }); +}); diff --git a/frontend/src/features/ui-standard/pages/UiStandardPage.vue.js b/frontend/src/features/ui-standard/pages/UiStandardPage.vue.js new file mode 100644 index 00000000..ecedd831 --- /dev/null +++ b/frontend/src/features/ui-standard/pages/UiStandardPage.vue.js @@ -0,0 +1,185 @@ +import { computed, ref } from 'vue'; +import { SearchListCrudPage } from '@/shared/ui/screen-types'; +import { screenTemplateCatalogue } from '@/shared/ui/screen-types/catalogue'; +import { KsButton, KsDataGrid, KsSelect, KsStatusTag, KsTextField } from '@/shared/ui/components'; +import { useUiAdapter } from '@/shared/ui/adapter/useUiAdapter'; +const adapter = useUiAdapter(); +const query = ref(''); +const status = ref('ALL'); +const options = [{ label: '전체', value: 'ALL' }, { label: '검토 필요', value: 'REVIEW' }, { label: '보류', value: 'HOLD' }]; +const rows = computed(() => screenTemplateCatalogue.filter(x => !query.value || `${x.id} ${x.name} ${x.component}`.toLowerCase().includes(query.value.toLowerCase())).map(x => ({ id: x.id, name: x.name, component: x.component, evidence: x.mandatoryEvidence.length, state: 'READY' }))); +const columns = [{ field: 'id', header: '화면 ID', width: 100 }, { field: 'name', header: '화면 타입' }, { field: 'component', header: '표준 컴포넌트', minWidth: 220 }, { field: 'evidence', header: '필수 증거', width: 110 }, { field: 'state', header: '상태', width: 110 }]; +const __VLS_ctx = { + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +/** @type {__VLS_StyleScopedClasses['summary']} */ ; +/** @type {__VLS_StyleScopedClasses['summary']} */ ; +/** @type {__VLS_StyleScopedClasses['detail']} */ ; +/** @type {__VLS_StyleScopedClasses['filters']} */ ; +let __VLS_0; +/** @ts-ignore @type { | typeof __VLS_components.SearchListCrudPage | typeof __VLS_components.SearchListCrudPage} */ +SearchListCrudPage; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + title: "표준 UI 패턴", + subtitle: "Feature는 공급자 라이브러리를 직접 사용하지 않고, v2 어댑터·레이아웃·화면 계약을 사용한다.", + state: "READY", + evidence: ({ asOf: '2026-08-02', version: 'UI-CONTRACT-2.0' }), +})); +const __VLS_2 = __VLS_1({ + title: "표준 UI 패턴", + subtitle: "Feature는 공급자 라이브러리를 직접 사용하지 않고, v2 어댑터·레이아웃·화면 계약을 사용한다.", + state: "READY", + evidence: ({ asOf: '2026-08-02', version: 'UI-CONTRACT-2.0' }), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +{ + const { actions: __VLS_7 } = __VLS_3.slots; + let __VLS_8; + /** @ts-ignore @type { | typeof __VLS_components.KsButton} */ + KsButton; + // @ts-ignore + const __VLS_9 = __VLS_asFunctionalComponent1(__VLS_8, new __VLS_8({ + label: "새 화면 패킷", + severity: "secondary", + })); + const __VLS_10 = __VLS_9({ + label: "새 화면 패킷", + severity: "secondary", + }, ...__VLS_functionalComponentArgsRest(__VLS_9)); +} +{ + const { summary: __VLS_13 } = __VLS_3.slots; + __VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card summary" }, + }); + /** @type {__VLS_StyleScopedClasses['ks-card']} */ ; + /** @type {__VLS_StyleScopedClasses['summary']} */ ; + __VLS_asFunctionalElement1(__VLS_intrinsics.strong, __VLS_intrinsics.strong)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card summary" }, + }); + /** @type {__VLS_StyleScopedClasses['ks-card']} */ ; + /** @type {__VLS_StyleScopedClasses['summary']} */ ; + __VLS_asFunctionalElement1(__VLS_intrinsics.strong, __VLS_intrinsics.strong)({}); + (__VLS_ctx.adapter.descriptor.capabilities.size); + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card summary" }, + }); + /** @type {__VLS_StyleScopedClasses['ks-card']} */ ; + /** @type {__VLS_StyleScopedClasses['summary']} */ ; + let __VLS_14; + /** @ts-ignore @type { | typeof __VLS_components.KsStatusTag} */ + KsStatusTag; + // @ts-ignore + const __VLS_15 = __VLS_asFunctionalComponent1(__VLS_14, new __VLS_14({ + value: (__VLS_ctx.adapter.descriptor.id), + severity: "info", + })); + const __VLS_16 = __VLS_15({ + value: (__VLS_ctx.adapter.descriptor.id), + severity: "info", + }, ...__VLS_functionalComponentArgsRest(__VLS_15)); + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({}); + (__VLS_ctx.adapter.descriptor.vendor); + __VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card summary" }, + }); + /** @type {__VLS_StyleScopedClasses['ks-card']} */ ; + /** @type {__VLS_StyleScopedClasses['summary']} */ ; + let __VLS_19; + /** @ts-ignore @type { | typeof __VLS_components.KsStatusTag} */ + KsStatusTag; + // @ts-ignore + const __VLS_20 = __VLS_asFunctionalComponent1(__VLS_19, new __VLS_19({ + value: "자동주문 OFF", + severity: "warning", + })); + const __VLS_21 = __VLS_20({ + value: "자동주문 OFF", + severity: "warning", + }, ...__VLS_functionalComponentArgsRest(__VLS_20)); + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({}); + // @ts-ignore + [adapter, adapter, adapter,]; +} +{ + const { filters: __VLS_24 } = __VLS_3.slots; + __VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "filters" }, + }); + /** @type {__VLS_StyleScopedClasses['filters']} */ ; + let __VLS_25; + /** @ts-ignore @type { | typeof __VLS_components.KsTextField} */ + KsTextField; + // @ts-ignore + const __VLS_26 = __VLS_asFunctionalComponent1(__VLS_25, new __VLS_25({ + modelValue: (__VLS_ctx.query), + label: "검색", + placeholder: "화면 ID, 타입 또는 컴포넌트", + })); + const __VLS_27 = __VLS_26({ + modelValue: (__VLS_ctx.query), + label: "검색", + placeholder: "화면 ID, 타입 또는 컴포넌트", + }, ...__VLS_functionalComponentArgsRest(__VLS_26)); + let __VLS_30; + /** @ts-ignore @type { | typeof __VLS_components.KsSelect} */ + KsSelect; + // @ts-ignore + const __VLS_31 = __VLS_asFunctionalComponent1(__VLS_30, new __VLS_30({ + modelValue: (__VLS_ctx.status), + label: "상태", + options: (__VLS_ctx.options), + })); + const __VLS_32 = __VLS_31({ + modelValue: (__VLS_ctx.status), + label: "상태", + options: (__VLS_ctx.options), + }, ...__VLS_functionalComponentArgsRest(__VLS_31)); + // @ts-ignore + [query, status, options,]; +} +let __VLS_35; +/** @ts-ignore @type { | typeof __VLS_components.KsDataGrid} */ +KsDataGrid; +// @ts-ignore +const __VLS_36 = __VLS_asFunctionalComponent1(__VLS_35, new __VLS_35({ + rows: (__VLS_ctx.rows), + columns: (__VLS_ctx.columns), + height: "25rem", +})); +const __VLS_37 = __VLS_36({ + rows: (__VLS_ctx.rows), + columns: (__VLS_ctx.columns), + height: "25rem", +}, ...__VLS_functionalComponentArgsRest(__VLS_36)); +{ + const { detail: __VLS_40 } = __VLS_3.slots; + __VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card detail" }, + }); + /** @type {__VLS_StyleScopedClasses['ks-card']} */ ; + /** @type {__VLS_StyleScopedClasses['detail']} */ ; + __VLS_asFunctionalElement1(__VLS_intrinsics.h2, __VLS_intrinsics.h2)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.code, __VLS_intrinsics.code)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({}); + // @ts-ignore + [rows, columns,]; +} +// @ts-ignore +[]; +var __VLS_3; +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({}); +export default {}; diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 00000000..a0a25492 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,14 @@ +import { createApp } from 'vue'; +import { createPinia } from 'pinia'; +import { VueQueryPlugin } from '@tanstack/vue-query'; +import App from './App.vue'; +import { router } from './app/router'; +import { queryClient } from './app/queryClient'; +import { resolveUiProvider } from './shared/ui/provider'; +import './design-system/base.css'; +const app = createApp(App); +app.use(createPinia()); +app.use(router); +app.use(VueQueryPlugin, { queryClient }); +resolveUiProvider(import.meta.env.VITE_UI_ADAPTER).install(app); +app.mount('#app'); diff --git a/frontend/src/shared/api/client.js b/frontend/src/shared/api/client.js new file mode 100644 index 00000000..0028a5e1 --- /dev/null +++ b/frontend/src/shared/api/client.js @@ -0,0 +1,18 @@ +import axios from 'axios'; +import { ApiProblem } from './problem'; +export const api = axios.create({ baseURL: '/api', timeout: 15_000 }); +api.interceptors.request.use(config => { + const user = import.meta.env.VITE_DEV_AUTH_USER; + const role = import.meta.env.VITE_DEV_AUTH_ROLE; + if (import.meta.env.DEV && user && role) { + config.headers['X-KArtSell-User'] = user; + config.headers['X-KArtSell-Role'] = role; + } + return config; +}); +api.interceptors.response.use(response => response, error => { + const data = error.response?.data; + if (data?.status) + throw new ApiProblem(data); + throw error; +}); diff --git a/frontend/src/shared/api/problem.js b/frontend/src/shared/api/problem.js new file mode 100644 index 00000000..ecfd6914 --- /dev/null +++ b/frontend/src/shared/api/problem.js @@ -0,0 +1,8 @@ +export class ApiProblem extends Error { + problem; + constructor(problem) { + super(problem.title); + this.problem = problem; + } + get status() { return this.problem.status; } +} diff --git a/frontend/src/shared/auth/PermissionGuard.vue.js b/frontend/src/shared/auth/PermissionGuard.vue.js new file mode 100644 index 00000000..ece04a88 --- /dev/null +++ b/frontend/src/shared/auth/PermissionGuard.vue.js @@ -0,0 +1,25 @@ +const props = defineProps(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +if (props.allowed) { + var __VLS_0 = {}; +} +else { + __VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({ + role: "alert", + }); + (props.deniedMessage ?? '이 기능을 사용할 권한이 없습니다.'); +} +// @ts-ignore +var __VLS_1 = __VLS_0; +const __VLS_base = (await import('vue')).defineComponent({ + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/commands/idempotency.js b/frontend/src/shared/commands/idempotency.js new file mode 100644 index 00000000..5ce785a3 --- /dev/null +++ b/frontend/src/shared/commands/idempotency.js @@ -0,0 +1,9 @@ +export function createIdempotencyKey() { + return crypto.randomUUID(); +} +/** + * Create once per user intent. Retries must reuse the returned envelope rather than call this again. + */ +export function createIdempotentCommand(request) { + return Object.freeze({ idempotencyKey: createIdempotencyKey(), request }); +} diff --git a/frontend/src/shared/contracts/versionSet.js b/frontend/src/shared/contracts/versionSet.js new file mode 100644 index 00000000..9d6e8ef4 --- /dev/null +++ b/frontend/src/shared/contracts/versionSet.js @@ -0,0 +1,9 @@ +import { z } from 'zod'; +export const versionSetSchema = z.object({ + datasetId: z.string().min(1).max(128), + dataHash: z.string().min(1).max(128), + modelVersion: z.string().min(1).max(128), + configVersion: z.string().min(1).max(128), + codeSha: z.string().min(1).max(128), + contractVersion: z.string().min(1).max(64) +}); diff --git a/frontend/src/shared/crud/StandardCrudFormPage.vue.js b/frontend/src/shared/crud/StandardCrudFormPage.vue.js new file mode 100644 index 00000000..59e5eb63 --- /dev/null +++ b/frontend/src/shared/crud/StandardCrudFormPage.vue.js @@ -0,0 +1,111 @@ +import PageLayout from '../ui/layouts/PageLayout.vue'; +import FormPageLayout from '../ui/layouts/FormPageLayout.vue'; +import StandardScreenBoundary from '../ui/screen-types/v2/StandardScreenBoundary.vue'; +const __VLS_props = withDefaults(defineProps(), { state: 'READY', dirty: false, readonly: false }); +const emit = defineEmits(); +const __VLS_defaults = { state: 'READY', dirty: false, readonly: false }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = PageLayout || PageLayout; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + title: (__VLS_ctx.title), + subtitle: (__VLS_ctx.subtitle), + status: (__VLS_ctx.readonly ? 'READONLY' : __VLS_ctx.state), + asOf: (__VLS_ctx.asOf), + version: (__VLS_ctx.version), +})); +const __VLS_2 = __VLS_1({ + title: (__VLS_ctx.title), + subtitle: (__VLS_ctx.subtitle), + status: (__VLS_ctx.readonly ? 'READONLY' : __VLS_ctx.state), + asOf: (__VLS_ctx.asOf), + version: (__VLS_ctx.version), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +const __VLS_7 = StandardScreenBoundary || StandardScreenBoundary; +// @ts-ignore +const __VLS_8 = __VLS_asFunctionalComponent1(__VLS_7, new __VLS_7({ + ...{ 'onRetry': {} }, + state: (__VLS_ctx.readonly ? 'READONLY' : (__VLS_ctx.dirty ? 'DIRTY' : __VLS_ctx.state)), + staleAt: (__VLS_ctx.asOf), +})); +const __VLS_9 = __VLS_8({ + ...{ 'onRetry': {} }, + state: (__VLS_ctx.readonly ? 'READONLY' : (__VLS_ctx.dirty ? 'DIRTY' : __VLS_ctx.state)), + staleAt: (__VLS_ctx.asOf), +}, ...__VLS_functionalComponentArgsRest(__VLS_8)); +let __VLS_12; +const __VLS_13 = { + /** @type {typeof __VLS_12.retry} */ + onRetry: (...[$event]) => { + return (__VLS_ctx.emit('retry')); + // @ts-ignore + [title, subtitle, readonly, readonly, state, state, asOf, asOf, version, dirty, emit,]; + }, +}; +const { default: __VLS_14 } = __VLS_10.slots; +const __VLS_15 = FormPageLayout || FormPageLayout; +// @ts-ignore +const __VLS_16 = __VLS_asFunctionalComponent1(__VLS_15, new __VLS_15({ + ...{ 'onSubmit': {} }, +})); +const __VLS_17 = __VLS_16({ + ...{ 'onSubmit': {} }, +}, ...__VLS_functionalComponentArgsRest(__VLS_16)); +let __VLS_20; +const __VLS_21 = { + /** @type {typeof __VLS_20.submit} */ + onSubmit: (...[$event]) => { + return (__VLS_ctx.emit('submit')); + // @ts-ignore + [emit,]; + }, +}; +const { default: __VLS_22 } = __VLS_18.slots; +var __VLS_23 = {}; +if (__VLS_ctx.$slots.aside) { + { + const { preview: __VLS_25 } = __VLS_18.slots; + var __VLS_26 = {}; + // @ts-ignore + [$slots,]; + } +} +// @ts-ignore +[]; +var __VLS_18; +var __VLS_19; +// @ts-ignore +[]; +var __VLS_10; +var __VLS_11; +{ + const { footer: __VLS_28 } = __VLS_3.slots; + var __VLS_29 = {}; + // @ts-ignore + []; +} +// @ts-ignore +[]; +var __VLS_3; +// @ts-ignore +var __VLS_24 = __VLS_23, __VLS_27 = __VLS_26, __VLS_30 = __VLS_29; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/crud/StandardCrudListPage.vue.js b/frontend/src/shared/crud/StandardCrudListPage.vue.js new file mode 100644 index 00000000..da167865 --- /dev/null +++ b/frontend/src/shared/crud/StandardCrudListPage.vue.js @@ -0,0 +1,159 @@ +import PageLayout from '../ui/layouts/PageLayout.vue'; +import StandardScreenBoundary from '../ui/screen-types/v2/StandardScreenBoundary.vue'; +import KsDataGrid from '../ui/components/KsDataGrid.vue'; +import KsPaginator from '../ui/components/KsPaginator.vue'; +const __VLS_props = withDefaults(defineProps(), { state: 'READY', rows: () => [] }); +const emit = defineEmits(); +const __VLS_defaults = { state: 'READY', rows: () => [] }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = PageLayout || PageLayout; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + title: (__VLS_ctx.title), + subtitle: (__VLS_ctx.subtitle), + status: (__VLS_ctx.state), + asOf: (__VLS_ctx.asOf), + version: (__VLS_ctx.version), +})); +const __VLS_2 = __VLS_1({ + title: (__VLS_ctx.title), + subtitle: (__VLS_ctx.subtitle), + status: (__VLS_ctx.state), + asOf: (__VLS_ctx.asOf), + version: (__VLS_ctx.version), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +{ + const { actions: __VLS_7 } = __VLS_3.slots; + var __VLS_8 = {}; + // @ts-ignore + [title, subtitle, state, asOf, version,]; +} +{ + const { summary: __VLS_10 } = __VLS_3.slots; + var __VLS_11 = {}; + // @ts-ignore + []; +} +{ + const { filters: __VLS_13 } = __VLS_3.slots; + var __VLS_14 = {}; + // @ts-ignore + []; +} +const __VLS_16 = StandardScreenBoundary || StandardScreenBoundary; +// @ts-ignore +const __VLS_17 = __VLS_asFunctionalComponent1(__VLS_16, new __VLS_16({ + ...{ 'onRetry': {} }, + state: (__VLS_ctx.state), + warning: (__VLS_ctx.warning), + staleAt: (__VLS_ctx.asOf), +})); +const __VLS_18 = __VLS_17({ + ...{ 'onRetry': {} }, + state: (__VLS_ctx.state), + warning: (__VLS_ctx.warning), + staleAt: (__VLS_ctx.asOf), +}, ...__VLS_functionalComponentArgsRest(__VLS_17)); +let __VLS_21; +const __VLS_22 = { + /** @type {typeof __VLS_21.retry} */ + onRetry: (...[$event]) => { + return (__VLS_ctx.emit('retry')); + // @ts-ignore + [state, asOf, warning, emit,]; + }, +}; +const { default: __VLS_23 } = __VLS_19.slots; +const __VLS_24 = KsDataGrid; +// @ts-ignore +const __VLS_25 = __VLS_asFunctionalComponent1(__VLS_24, new __VLS_24({ + ...{ 'onRowSelected': {} }, + rows: (__VLS_ctx.rows), + columns: (__VLS_ctx.columns), +})); +const __VLS_26 = __VLS_25({ + ...{ 'onRowSelected': {} }, + rows: (__VLS_ctx.rows), + columns: (__VLS_ctx.columns), +}, ...__VLS_functionalComponentArgsRest(__VLS_25)); +let __VLS_29; +const __VLS_30 = { + /** @type {typeof __VLS_29.rowSelected} */ + onRowSelected: (...[$event]) => { + return (__VLS_ctx.emit('rowSelected', $event)); + // @ts-ignore + [emit, rows, columns,]; + }, +}; +var __VLS_27; +var __VLS_28; +const __VLS_31 = KsPaginator; +// @ts-ignore +const __VLS_32 = __VLS_asFunctionalComponent1(__VLS_31, new __VLS_31({ + ...{ 'onPageChange': {} }, + page: (__VLS_ctx.page), + pageSize: (__VLS_ctx.pageSize), + total: (__VLS_ctx.total), +})); +const __VLS_33 = __VLS_32({ + ...{ 'onPageChange': {} }, + page: (__VLS_ctx.page), + pageSize: (__VLS_ctx.pageSize), + total: (__VLS_ctx.total), +}, ...__VLS_functionalComponentArgsRest(__VLS_32)); +let __VLS_36; +const __VLS_37 = { + /** @type {typeof __VLS_36.pageChange} */ + onPageChange: (...[$event]) => { + return (__VLS_ctx.emit('pageChange', $event)); + // @ts-ignore + [emit, page, pageSize, total,]; + }, +}; +var __VLS_34; +var __VLS_35; +// @ts-ignore +[]; +var __VLS_19; +var __VLS_20; +if (__VLS_ctx.$slots.detail) { + { + const { aside: __VLS_38 } = __VLS_3.slots; + var __VLS_39 = {}; + // @ts-ignore + [$slots,]; + } +} +if (__VLS_ctx.$slots.footer) { + { + const { footer: __VLS_41 } = __VLS_3.slots; + var __VLS_42 = {}; + // @ts-ignore + [$slots,]; + } +} +// @ts-ignore +[]; +var __VLS_3; +// @ts-ignore +var __VLS_9 = __VLS_8, __VLS_12 = __VLS_11, __VLS_15 = __VLS_14, __VLS_40 = __VLS_39, __VLS_43 = __VLS_42; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/crud/contracts.js b/frontend/src/shared/crud/contracts.js new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/frontend/src/shared/crud/contracts.js @@ -0,0 +1 @@ +export {}; diff --git a/frontend/src/shared/crud/queryCodec.js b/frontend/src/shared/crud/queryCodec.js new file mode 100644 index 00000000..de176848 --- /dev/null +++ b/frontend/src/shared/crud/queryCodec.js @@ -0,0 +1,41 @@ +const positiveInt = (value, fallback) => { + const parsed = Number(value); + return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback; +}; +export function decodeCrudQuery(params, fallback) { + const sorts = params.getAll('sort').flatMap(value => { + const parts = value.split(':'); + const field = parts[0]; + const direction = parts[1]; + if (field && (direction === 'asc' || direction === 'desc')) { + return [{ field, direction: direction }]; + } + return []; + }); + const filters = params.getAll('filter').flatMap(value => { + const first = value.indexOf(':'); + const second = value.indexOf(':', first + 1); + if (first <= 0 || second <= first) + return []; + return [{ field: value.slice(0, first), operator: value.slice(first + 1, second), value: value.slice(second + 1) }]; + }); + return { + page: positiveInt(params.get('page'), fallback.page), + pageSize: positiveInt(params.get('pageSize'), fallback.pageSize), + search: params.get('search')?.trim() || undefined, + sorts: sorts.length ? sorts : fallback.sorts, + filters: filters.length ? filters : fallback.filters + }; +} +export function encodeCrudQuery(query) { + const params = new URLSearchParams(); + params.set('page', String(query.page)); + params.set('pageSize', String(query.pageSize)); + if (query.search) + params.set('search', query.search); + for (const sort of query.sorts) + params.append('sort', `${sort.field}:${sort.direction}`); + for (const filter of query.filters) + params.append('filter', `${filter.field}:${filter.operator}:${String(filter.value ?? '')}`); + return params; +} diff --git a/frontend/src/shared/crud/resourceDefinition.js b/frontend/src/shared/crud/resourceDefinition.js new file mode 100644 index 00000000..a3ee5bbc --- /dev/null +++ b/frontend/src/shared/crud/resourceDefinition.js @@ -0,0 +1,8 @@ +export function assertCrudResourceDefinition(definition) { + if (!definition.resourceId.trim()) + throw new Error('resourceId is required'); + const fields = new Set(definition.columns.map(x => x.field)); + for (const sensitive of definition.sensitiveFields) + if (!fields.has(sensitive)) + throw new Error(`Sensitive field '${sensitive}' has no grid column contract`); +} diff --git a/frontend/src/shared/crud/tests/queryCodec.spec.js b/frontend/src/shared/crud/tests/queryCodec.spec.js new file mode 100644 index 00000000..872f3ee6 --- /dev/null +++ b/frontend/src/shared/crud/tests/queryCodec.spec.js @@ -0,0 +1,12 @@ +import { describe, expect, it } from 'vitest'; +import { decodeCrudQuery, encodeCrudQuery } from '../queryCodec'; +const fallback = { page: 1, pageSize: 20, sorts: [], filters: [] }; +describe('CRUD URL codec', () => { + it('round-trips paging, sort, filter and search without hidden Pinia state', () => { + const source = { page: 3, pageSize: 50, search: '005930', sorts: [{ field: 'asOf', direction: 'desc' }], filters: [{ field: 'status', operator: 'eq', value: 'WARN' }] }; + expect(decodeCrudQuery(encodeCrudQuery(source), fallback)).toEqual(source); + }); + it('fails closed to approved defaults for invalid paging', () => { + expect(decodeCrudQuery(new URLSearchParams('page=0&pageSize=-1'), fallback)).toEqual({ ...fallback, search: undefined }); + }); +}); diff --git a/frontend/src/shared/crud/useCrudListState.js b/frontend/src/shared/crud/useCrudListState.js new file mode 100644 index 00000000..c39e3493 --- /dev/null +++ b/frontend/src/shared/crud/useCrudListState.js @@ -0,0 +1,20 @@ +import { computed, ref } from 'vue'; +export function useCrudListState(initial) { + const query = ref({ ...initial, sorts: [...initial.sorts], filters: [...initial.filters] }); + const selectedId = ref(null); + const dirty = ref(false); + function replace(next) { query.value = { ...next, sorts: [...next.sorts], filters: [...next.filters] }; } + function setPage(page, pageSize = query.value.pageSize) { query.value = { ...query.value, page, pageSize }; } + function setSearch(search) { query.value = { ...query.value, page: 1, search: search?.trim() || undefined }; } + function reset() { replace(initial); selectedId.value = null; dirty.value = false; } + return { + query, + selectedId, + dirty, + offset: computed(() => (query.value.page - 1) * query.value.pageSize), + replace, + setPage, + setSearch, + reset + }; +} diff --git a/frontend/src/shared/crud/useOptimisticCommand.js b/frontend/src/shared/crud/useOptimisticCommand.js new file mode 100644 index 00000000..bd393481 --- /dev/null +++ b/frontend/src/shared/crud/useOptimisticCommand.js @@ -0,0 +1,31 @@ +import { ref } from 'vue'; +import { createIdempotencyKey } from '../commands/idempotency'; +export function useOptimisticCommand(execute) { + const pending = ref(false); + const conflict = ref(false); + const lastCorrelationId = ref(); + async function run(request) { + if (pending.value) + throw new Error('Command is already in progress'); + pending.value = true; + conflict.value = false; + try { + const headers = { 'Idempotency-Key': createIdempotencyKey() }; + if (request.etag) + headers['If-Match'] = request.etag; + const response = await execute(request, headers); + lastCorrelationId.value = response.correlationId; + return response; + } + catch (error) { + const status = error?.response?.status; + if (status === 409 || status === 412) + conflict.value = true; + throw error; + } + finally { + pending.value = false; + } + } + return { pending, conflict, lastCorrelationId, run }; +} diff --git a/frontend/src/shared/formatters/financial.js b/frontend/src/shared/formatters/financial.js new file mode 100644 index 00000000..e71001b2 --- /dev/null +++ b/frontend/src/shared/formatters/financial.js @@ -0,0 +1,23 @@ +export function formatCurrency(value, currency, locale = 'ko-KR') { + if (value == null || Number.isNaN(value)) + return '—'; + return new Intl.NumberFormat(locale, { style: 'currency', currency, maximumFractionDigits: 2 }).format(value); +} +export function formatPercent(value, digits = 2, locale = 'ko-KR') { + if (value == null || Number.isNaN(value)) + return '—'; + return new Intl.NumberFormat(locale, { style: 'percent', minimumFractionDigits: digits, maximumFractionDigits: digits }).format(value); +} +export function formatQuantity(value, digits = 4, locale = 'ko-KR') { + if (value == null || Number.isNaN(value)) + return '—'; + return new Intl.NumberFormat(locale, { maximumFractionDigits: digits }).format(value); +} +export function formatAsOf(value, locale = 'ko-KR') { + if (!value) + return '—'; + const date = value instanceof Date ? value : new Date(value); + if (Number.isNaN(date.getTime())) + return '—'; + return new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short', timeZone: 'Asia/Seoul' }).format(date); +} diff --git a/frontend/src/shared/formatters/tests/financial.spec.js b/frontend/src/shared/formatters/tests/financial.spec.js new file mode 100644 index 00000000..e0fe75ae --- /dev/null +++ b/frontend/src/shared/formatters/tests/financial.spec.js @@ -0,0 +1,7 @@ +import { describe, expect, it } from 'vitest'; +import { formatCurrency, formatPercent, formatQuantity } from '../financial'; +describe('financial formatters', () => { + it('renders missing values as an explicit em dash', () => { expect(formatCurrency(null, 'KRW')).toBe('—'); expect(formatPercent(undefined)).toBe('—'); }); + it('keeps percentage inputs in decimal-return units', () => { expect(formatPercent(0.125, 1)).toContain('12.5'); }); + it('uses bounded quantity precision', () => { expect(formatQuantity(1.234567, 2)).toContain('1.23'); }); +}); diff --git a/frontend/src/shared/status/DataFreshnessBadge.vue.js b/frontend/src/shared/status/DataFreshnessBadge.vue.js new file mode 100644 index 00000000..335a168d --- /dev/null +++ b/frontend/src/shared/status/DataFreshnessBadge.vue.js @@ -0,0 +1,25 @@ +import { computed } from 'vue'; +const props = defineProps(); +const ageMinutes = computed(() => Math.max(0, (Date.now() - new Date(props.asOf).getTime()) / 60_000)); +const stale = computed(() => ageMinutes.value > props.staleAfterMinutes); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({ + 'aria-label': (__VLS_ctx.stale ? '데이터 지연' : '데이터 최신'), + 'data-status': (__VLS_ctx.stale ? 'stale' : 'fresh'), +}); +(__VLS_ctx.stale ? 'STALE' : 'FRESH'); +(new Date(props.asOf).toLocaleString()); +// @ts-ignore +[stale, stale, stale,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/DataGridShell.vue.js b/frontend/src/shared/ui/DataGridShell.vue.js new file mode 100644 index 00000000..c1a40aec --- /dev/null +++ b/frontend/src/shared/ui/DataGridShell.vue.js @@ -0,0 +1,46 @@ +import { KsDataGrid } from './components'; +const __VLS_props = withDefaults(defineProps(), { loading: false, emptyMessage: '표시할 데이터가 없습니다.' }); +const __VLS_defaults = { loading: false, emptyMessage: '표시할 데이터가 없습니다.' }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + 'aria-label': "데이터 표", + 'aria-busy': (__VLS_ctx.loading), +}); +if (__VLS_ctx.loading) { + __VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({}); +} +else if (__VLS_ctx.rows.length === 0) { + __VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({}); + (__VLS_ctx.emptyMessage); +} +else { + let __VLS_0; + /** @ts-ignore @type { | typeof __VLS_components.KsDataGrid} */ + KsDataGrid; + // @ts-ignore + const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + rows: (__VLS_ctx.rows), + columns: (__VLS_ctx.columns), + height: "30rem", + })); + const __VLS_2 = __VLS_1({ + rows: (__VLS_ctx.rows), + columns: (__VLS_ctx.columns), + height: "30rem", + }, ...__VLS_functionalComponentArgsRest(__VLS_1)); +} +// @ts-ignore +[loading, loading, rows, rows, emptyMessage, columns,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeProps: {}, + props: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/EvidenceVersionSet.vue.js b/frontend/src/shared/ui/EvidenceVersionSet.vue.js new file mode 100644 index 00000000..ab87ff62 --- /dev/null +++ b/frontend/src/shared/ui/EvidenceVersionSet.vue.js @@ -0,0 +1,38 @@ +const props = defineProps(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.dl, __VLS_intrinsics.dl)({ + ...{ class: "version-set" }, + 'data-compact': (props.compact ? 'true' : 'false'), +}); +/** @type {__VLS_StyleScopedClasses['version-set']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); +(props.value.datasetId); +__VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.code, __VLS_intrinsics.code)({}); +(props.value.dataHash); +__VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); +(props.value.modelVersion); +__VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); +(props.value.configVersion); +__VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.code, __VLS_intrinsics.code)({}); +(props.value.codeSha); +__VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); +(props.value.contractVersion); +const __VLS_export = (await import('vue')).defineComponent({ + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/QueryStateBoundary.vue.js b/frontend/src/shared/ui/QueryStateBoundary.vue.js new file mode 100644 index 00000000..4f9713da --- /dev/null +++ b/frontend/src/shared/ui/QueryStateBoundary.vue.js @@ -0,0 +1,232 @@ +import KsButton from './components/KsButton.vue'; +import KsInlineMessage from './components/KsInlineMessage.vue'; +const props = defineProps(); +const emit = defineEmits(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + 'aria-busy': (props.loading || props.processing), +}); +if (props.loading) { + const __VLS_0 = KsInlineMessage; + // @ts-ignore + const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + severity: "info", + message: "불러오는 중입니다.", + })); + const __VLS_2 = __VLS_1({ + severity: "info", + message: "불러오는 중입니다.", + }, ...__VLS_functionalComponentArgsRest(__VLS_1)); +} +else if (props.unauthorized) { + const __VLS_5 = KsInlineMessage; + // @ts-ignore + const __VLS_6 = __VLS_asFunctionalComponent1(__VLS_5, new __VLS_5({ + severity: "warning", + title: "로그인 필요", + message: "로그인 후 다시 시도하세요.", + })); + const __VLS_7 = __VLS_6({ + severity: "warning", + title: "로그인 필요", + message: "로그인 후 다시 시도하세요.", + }, ...__VLS_functionalComponentArgsRest(__VLS_6)); +} +else if (props.forbidden) { + const __VLS_10 = KsInlineMessage; + // @ts-ignore + const __VLS_11 = __VLS_asFunctionalComponent1(__VLS_10, new __VLS_10({ + severity: "danger", + title: "권한 없음", + message: "이 작업을 수행할 권한이 없습니다.", + })); + const __VLS_12 = __VLS_11({ + severity: "danger", + title: "권한 없음", + message: "이 작업을 수행할 권한이 없습니다.", + }, ...__VLS_functionalComponentArgsRest(__VLS_11)); +} +else if (props.conflict) { + const __VLS_15 = KsInlineMessage; + // @ts-ignore + const __VLS_16 = __VLS_asFunctionalComponent1(__VLS_15, new __VLS_15({ + severity: "warning", + title: "변경 충돌", + message: "다른 사용자가 먼저 변경했습니다. 최신 버전을 확인하세요.", + })); + const __VLS_17 = __VLS_16({ + severity: "warning", + title: "변경 충돌", + message: "다른 사용자가 먼저 변경했습니다. 최신 버전을 확인하세요.", + }, ...__VLS_functionalComponentArgsRest(__VLS_16)); +} +else if (props.expired) { + const __VLS_20 = KsInlineMessage; + // @ts-ignore + const __VLS_21 = __VLS_asFunctionalComponent1(__VLS_20, new __VLS_20({ + severity: "warning", + title: "유효기간 만료", + message: "만료된 증거 또는 제안은 실행·공개할 수 없습니다.", + })); + const __VLS_22 = __VLS_21({ + severity: "warning", + title: "유효기간 만료", + message: "만료된 증거 또는 제안은 실행·공개할 수 없습니다.", + }, ...__VLS_functionalComponentArgsRest(__VLS_21)); +} +else if (props.error) { + __VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + role: "alert", + ...{ class: "ks-state-error" }, + }); + /** @type {__VLS_StyleScopedClasses['ks-state-error']} */ ; + const __VLS_25 = KsInlineMessage; + // @ts-ignore + const __VLS_26 = __VLS_asFunctionalComponent1(__VLS_25, new __VLS_25({ + severity: "danger", + title: "요청 실패", + message: (props.error.message), + })); + const __VLS_27 = __VLS_26({ + severity: "danger", + title: "요청 실패", + message: (props.error.message), + }, ...__VLS_functionalComponentArgsRest(__VLS_26)); + const __VLS_30 = KsButton; + // @ts-ignore + const __VLS_31 = __VLS_asFunctionalComponent1(__VLS_30, new __VLS_30({ + ...{ 'onClick': {} }, + label: "같은 요청 다시 시도", + severity: "secondary", + })); + const __VLS_32 = __VLS_31({ + ...{ 'onClick': {} }, + label: "같은 요청 다시 시도", + severity: "secondary", + }, ...__VLS_functionalComponentArgsRest(__VLS_31)); + let __VLS_35; + const __VLS_36 = { + /** @type {typeof __VLS_35.click} */ + onClick: (...[$event]) => { + if (!!(props.loading)) + throw 0; + if (!!(props.unauthorized)) + throw 0; + if (!!(props.forbidden)) + throw 0; + if (!!(props.conflict)) + throw 0; + if (!!(props.expired)) + throw 0; + if (!(props.error)) + throw 0; + return (__VLS_ctx.emit('retry')); + // @ts-ignore + [emit,]; + }, + }; + var __VLS_33; + var __VLS_34; + if (__VLS_ctx.correlationId) { + __VLS_asFunctionalElement1(__VLS_intrinsics.small, __VLS_intrinsics.small)({}); + (__VLS_ctx.correlationId); + } +} +else if (props.empty) { + const __VLS_37 = KsInlineMessage; + // @ts-ignore + const __VLS_38 = __VLS_asFunctionalComponent1(__VLS_37, new __VLS_37({ + severity: "info", + message: "표시할 데이터가 없습니다.", + })); + const __VLS_39 = __VLS_38({ + severity: "info", + message: "표시할 데이터가 없습니다.", + }, ...__VLS_functionalComponentArgsRest(__VLS_38)); +} +else { + if (props.partial) { + const __VLS_42 = KsInlineMessage; + // @ts-ignore + const __VLS_43 = __VLS_asFunctionalComponent1(__VLS_42, new __VLS_42({ + severity: "warning", + message: "일부 데이터만 표시하고 있습니다. 완전성 경고를 확인하세요.", + })); + const __VLS_44 = __VLS_43({ + severity: "warning", + message: "일부 데이터만 표시하고 있습니다. 완전성 경고를 확인하세요.", + }, ...__VLS_functionalComponentArgsRest(__VLS_43)); + } + if (props.warning) { + const __VLS_47 = KsInlineMessage; + // @ts-ignore + const __VLS_48 = __VLS_asFunctionalComponent1(__VLS_47, new __VLS_47({ + severity: "warning", + message: (props.warning), + })); + const __VLS_49 = __VLS_48({ + severity: "warning", + message: (props.warning), + }, ...__VLS_functionalComponentArgsRest(__VLS_48)); + } + if (props.readonly) { + const __VLS_52 = KsInlineMessage; + // @ts-ignore + const __VLS_53 = __VLS_asFunctionalComponent1(__VLS_52, new __VLS_52({ + severity: "info", + message: "읽기 전용 상태입니다.", + })); + const __VLS_54 = __VLS_53({ + severity: "info", + message: "읽기 전용 상태입니다.", + }, ...__VLS_functionalComponentArgsRest(__VLS_53)); + } + if (props.dirty) { + const __VLS_57 = KsInlineMessage; + // @ts-ignore + const __VLS_58 = __VLS_asFunctionalComponent1(__VLS_57, new __VLS_57({ + severity: "warning", + message: "저장되지 않은 변경사항이 있습니다.", + })); + const __VLS_59 = __VLS_58({ + severity: "warning", + message: "저장되지 않은 변경사항이 있습니다.", + }, ...__VLS_functionalComponentArgsRest(__VLS_58)); + } + if (props.processing) { + const __VLS_62 = KsInlineMessage; + // @ts-ignore + const __VLS_63 = __VLS_asFunctionalComponent1(__VLS_62, new __VLS_62({ + severity: "info", + message: "처리 중입니다. 중복 제출하지 마세요.", + })); + const __VLS_64 = __VLS_63({ + severity: "info", + message: "처리 중입니다. 중복 제출하지 마세요.", + }, ...__VLS_functionalComponentArgsRest(__VLS_63)); + } + var __VLS_67 = {}; +} +if (props.staleAt) { + __VLS_asFunctionalElement1(__VLS_intrinsics.small, __VLS_intrinsics.small)({}); + (props.staleAt); +} +// @ts-ignore +var __VLS_68 = __VLS_67; +// @ts-ignore +[correlationId, correlationId,]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/VersionConflictDialog.vue.js b/frontend/src/shared/ui/VersionConflictDialog.vue.js new file mode 100644 index 00000000..8a8b7ba6 --- /dev/null +++ b/frontend/src/shared/ui/VersionConflictDialog.vue.js @@ -0,0 +1,47 @@ +const props = defineProps(); +const emit = defineEmits(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.dialog, __VLS_intrinsics.dialog)({ + open: (props.open), + 'aria-labelledby': "version-conflict-title", +}); +__VLS_asFunctionalElement1(__VLS_intrinsics.h2, __VLS_intrinsics.h2)({ + id: "version-conflict-title", +}); +__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({}); +if (props.currentVersion) { + __VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({}); + (props.currentVersion); +} +__VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({ + ...{ onClick: (...[$event]) => { + return (__VLS_ctx.emit('reload')); + // @ts-ignore + [emit,]; + } }, + type: "button", +}); +__VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({ + ...{ onClick: (...[$event]) => { + return (__VLS_ctx.emit('close')); + // @ts-ignore + [emit,]; + } }, + type: "button", +}); +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/compatibility.js b/frontend/src/shared/ui/adapter/compatibility.js new file mode 100644 index 00000000..30b152c3 --- /dev/null +++ b/frontend/src/shared/ui/adapter/compatibility.js @@ -0,0 +1,29 @@ +const componentByCapability = { + 'button': 'Button', 'text-field': 'TextField', 'text-area': 'TextArea', 'select': 'Select', + 'multi-select': 'MultiSelect', 'checkbox': 'Checkbox', 'date-field': 'DateField', + 'number-field': 'NumberField', 'dialog': 'Dialog', 'status-tag': 'StatusTag', + 'inline-message': 'InlineMessage', 'paginator': 'Paginator', 'tabs': 'Tabs', 'data-grid': 'DataGrid' +}; +export function evaluateUiAdapterCompatibility(adapter, requiredCapabilities, requireProductionEligible = false) { + const issues = []; + if (adapter.descriptor.contractVersion !== '4.0') { + issues.push({ code: 'CONTRACT_VERSION', severity: 'ERROR', detail: `Expected 4.0, got ${adapter.descriptor.contractVersion}` }); + } + for (const capability of requiredCapabilities) { + if (!adapter.descriptor.capabilities.has(capability)) { + issues.push({ code: 'MISSING_CAPABILITY', severity: 'ERROR', detail: capability }); + continue; + } + const component = adapter.components[componentByCapability[capability]]; + if (!component) + issues.push({ code: 'MISSING_COMPONENT', severity: 'ERROR', detail: capability }); + } + if (requireProductionEligible && !adapter.descriptor.productionEligible) { + issues.push({ code: 'PRODUCTION_INELIGIBLE', severity: 'ERROR', detail: adapter.descriptor.id }); + } + return { adapterId: adapter.descriptor.id, contractVersion: adapter.descriptor.contractVersion, compatible: !issues.some(x => x.severity === 'ERROR'), issues }; +} +export function assertUiAdapterCompatibility(report) { + if (!report.compatible) + throw new Error(`UI adapter ${report.adapterId} is incompatible: ${report.issues.map(x => `${x.code}:${x.detail}`).join(', ')}`); +} diff --git a/frontend/src/shared/ui/adapter/contracts.js b/frontend/src/shared/ui/adapter/contracts.js new file mode 100644 index 00000000..cc1836bb --- /dev/null +++ b/frontend/src/shared/ui/adapter/contracts.js @@ -0,0 +1,22 @@ +export const requiredUiAdapterCapabilities = Object.freeze([ + 'button', 'text-field', 'text-area', 'select', 'multi-select', 'checkbox', 'date-field', + 'number-field', 'dialog', 'status-tag', 'inline-message', 'paginator', 'tabs', 'data-grid' +]); +export function assertUiAdapterContract(adapter) { + if (adapter.descriptor.contractVersion !== '4.0') { + throw new Error(`Unsupported UI adapter contract: ${adapter.descriptor.contractVersion}`); + } + const missing = requiredUiAdapterCapabilities.filter(x => !adapter.descriptor.capabilities.has(x)); + if (missing.length > 0) { + throw new Error(`UI adapter ${adapter.descriptor.id} is missing capabilities: ${missing.join(', ')}`); + } + const componentNames = [ + 'Button', 'TextField', 'TextArea', 'Select', 'MultiSelect', 'Checkbox', 'DateField', + 'NumberField', 'Dialog', 'StatusTag', 'InlineMessage', 'Paginator', 'Tabs', 'DataGrid' + ]; + for (const name of componentNames) { + if (!adapter.components[name]) + throw new Error(`UI adapter ${adapter.descriptor.id} has no component for ${name}`); + } +} +export const uiAdapterKey = Symbol('KArtSellUiAdapterV4'); diff --git a/frontend/src/shared/ui/adapter/native/NativeButtonAdapter.vue.js b/frontend/src/shared/ui/adapter/native/NativeButtonAdapter.vue.js new file mode 100644 index 00000000..780acc34 --- /dev/null +++ b/frontend/src/shared/ui/adapter/native/NativeButtonAdapter.vue.js @@ -0,0 +1,43 @@ +const __VLS_props = withDefaults(defineProps(), { severity: 'primary', type: 'button', disabled: false, loading: false }); +const emit = defineEmits(); +const __VLS_defaults = { severity: 'primary', type: 'button', disabled: false, loading: false }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({ + ...{ onClick: (...[$event]) => { + return (__VLS_ctx.emit('activate', $event)); + // @ts-ignore + [emit,]; + } }, + ...{ class: "ks-native-button" }, + ...{ class: (`is-${__VLS_ctx.severity}`) }, + type: (__VLS_ctx.type), + disabled: (__VLS_ctx.disabled || __VLS_ctx.loading), +}); +/** @type {__VLS_StyleScopedClasses['ks-native-button']} */ ; +if (__VLS_ctx.loading) { + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({ + 'aria-hidden': "true", + }); +} +var __VLS_0 = {}; +(__VLS_ctx.label); +// @ts-ignore +var __VLS_1 = __VLS_0; +// @ts-ignore +[severity, type, disabled, loading, loading, label,]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/adapter/native/NativeCheckboxAdapter.vue.js b/frontend/src/shared/ui/adapter/native/NativeCheckboxAdapter.vue.js new file mode 100644 index 00000000..ee9f7e55 --- /dev/null +++ b/frontend/src/shared/ui/adapter/native/NativeCheckboxAdapter.vue.js @@ -0,0 +1,38 @@ +const __VLS_props = defineProps(); +const emit = defineEmits(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.input)({ + ...{ onChange: (...[$event]) => { + return (__VLS_ctx.emit('update:modelValue', $event.target.checked)); + // @ts-ignore + [emit,]; + } }, + ...{ onBlur: (...[$event]) => { + return (__VLS_ctx.emit('blur', $event)); + // @ts-ignore + [emit,]; + } }, + id: (__VLS_ctx.inputId), + ...{ class: "ks-native-checkbox" }, + type: "checkbox", + checked: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), + 'aria-invalid': (__VLS_ctx.invalid || undefined), +}); +/** @type {__VLS_StyleScopedClasses['ks-native-checkbox']} */ ; +// @ts-ignore +[inputId, modelValue, disabled, invalid,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/native/NativeDataGridAdapter.vue.js b/frontend/src/shared/ui/adapter/native/NativeDataGridAdapter.vue.js new file mode 100644 index 00000000..df2d3470 --- /dev/null +++ b/frontend/src/shared/ui/adapter/native/NativeDataGridAdapter.vue.js @@ -0,0 +1,79 @@ +const __VLS_props = withDefaults(defineProps(), { loading: false, height: '32rem', rowSelection: 'single' }); +const emit = defineEmits(); +function value(row, field) { return typeof row === 'object' && row !== null ? row[field] : undefined; } +const __VLS_defaults = { loading: false, height: '32rem', rowSelection: 'single' }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-native-grid" }, + ...{ style: ({ maxHeight: __VLS_ctx.height }) }, + 'aria-busy': (__VLS_ctx.loading), +}); +/** @type {__VLS_StyleScopedClasses['ks-native-grid']} */ ; +if (__VLS_ctx.loading) { + __VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({ + role: "status", + }); +} +__VLS_asFunctionalElement1(__VLS_intrinsics.table, __VLS_intrinsics.table)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.thead, __VLS_intrinsics.thead)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.tr, __VLS_intrinsics.tr)({}); +for (const [column] of __VLS_vFor((__VLS_ctx.columns))) { + __VLS_asFunctionalElement1(__VLS_intrinsics.th, __VLS_intrinsics.th)({ + key: (column.field), + scope: "col", + ...{ style: ({ width: column.width ? `${column.width}px` : undefined, minWidth: column.minWidth ? `${column.minWidth}px` : undefined }) }, + }); + (column.header); + // @ts-ignore + [height, loading, loading, columns,]; +} +__VLS_asFunctionalElement1(__VLS_intrinsics.tbody, __VLS_intrinsics.tbody)({}); +for (const [row, index] of __VLS_vFor((__VLS_ctx.rows))) { + __VLS_asFunctionalElement1(__VLS_intrinsics.tr, __VLS_intrinsics.tr)({ + ...{ onClick: (...[$event]) => { + return (__VLS_ctx.emit('row-selected', row)); + // @ts-ignore + [rows, emit,]; + } }, + ...{ onKeydown: (...[$event]) => { + return (__VLS_ctx.emit('row-selected', row)); + // @ts-ignore + [emit,]; + } }, + key: (index), + tabindex: "0", + }); + for (const [column] of __VLS_vFor((__VLS_ctx.columns))) { + __VLS_asFunctionalElement1(__VLS_intrinsics.td, __VLS_intrinsics.td)({ + key: (column.field), + }); + (column.formatter ? column.formatter(__VLS_ctx.value(row, column.field), row) : __VLS_ctx.value(row, column.field)); + // @ts-ignore + [columns, value, value,]; + } + // @ts-ignore + []; +} +if (!__VLS_ctx.loading && __VLS_ctx.rows.length === 0) { + __VLS_asFunctionalElement1(__VLS_intrinsics.tr, __VLS_intrinsics.tr)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.td, __VLS_intrinsics.td)({ + colspan: (__VLS_ctx.columns.length), + }); +} +// @ts-ignore +[loading, columns, rows,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/native/NativeDateFieldAdapter.vue.js b/frontend/src/shared/ui/adapter/native/NativeDateFieldAdapter.vue.js new file mode 100644 index 00000000..3be03e73 --- /dev/null +++ b/frontend/src/shared/ui/adapter/native/NativeDateFieldAdapter.vue.js @@ -0,0 +1,44 @@ +const __VLS_props = defineProps(); +const emit = defineEmits(); +function toDateValue(value) { if (!value) + return ''; if (value instanceof Date) + return value.toISOString().slice(0, 10); return value.slice(0, 10); } +function boundary(value) { return value?.toISOString().slice(0, 10); } +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.input)({ + ...{ onInput: (...[$event]) => { + return (__VLS_ctx.emit('update:modelValue', $event.target.value || null)); + // @ts-ignore + [emit,]; + } }, + ...{ onBlur: (...[$event]) => { + return (__VLS_ctx.emit('blur', $event)); + // @ts-ignore + [emit,]; + } }, + id: (__VLS_ctx.inputId), + ...{ class: "ks-native-input" }, + type: "date", + value: (__VLS_ctx.toDateValue(__VLS_ctx.modelValue)), + disabled: (__VLS_ctx.disabled), + 'aria-invalid': (__VLS_ctx.invalid || undefined), + min: (__VLS_ctx.boundary(__VLS_ctx.min)), + max: (__VLS_ctx.boundary(__VLS_ctx.max)), +}); +/** @type {__VLS_StyleScopedClasses['ks-native-input']} */ ; +// @ts-ignore +[inputId, toDateValue, modelValue, disabled, invalid, boundary, boundary, min, max,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/native/NativeDialogAdapter.vue.js b/frontend/src/shared/ui/adapter/native/NativeDialogAdapter.vue.js new file mode 100644 index 00000000..e4830681 --- /dev/null +++ b/frontend/src/shared/ui/adapter/native/NativeDialogAdapter.vue.js @@ -0,0 +1,48 @@ +import { nextTick, ref, watch } from 'vue'; +const props = defineProps(); +const emit = defineEmits(); +const element = ref(null); +watch(() => props.visible, async (visible) => { await nextTick(); const dialog = element.value; if (!dialog) + return; if (visible && !dialog.open) + props.modal === false ? dialog.show() : dialog.showModal(); if (!visible && dialog.open) + dialog.close(); }, { immediate: true }); +function close() { emit('update:visible', false); } +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.dialog, __VLS_intrinsics.dialog)({ + ...{ onClose: (__VLS_ctx.close) }, + ...{ onCancel: (__VLS_ctx.close) }, + ref: "element", + ...{ class: "ks-native-dialog" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-native-dialog']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.header, __VLS_intrinsics.header)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.h2, __VLS_intrinsics.h2)({}); +(__VLS_ctx.title); +__VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({ + ...{ onClick: (__VLS_ctx.close) }, + type: "button", + 'aria-label': "닫기", +}); +__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({}); +var __VLS_0 = {}; +__VLS_asFunctionalElement1(__VLS_intrinsics.footer, __VLS_intrinsics.footer)({}); +var __VLS_2 = {}; +// @ts-ignore +var __VLS_1 = __VLS_0, __VLS_3 = __VLS_2; +// @ts-ignore +[close, close, close, title,]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/adapter/native/NativeInlineMessageAdapter.vue.js b/frontend/src/shared/ui/adapter/native/NativeInlineMessageAdapter.vue.js new file mode 100644 index 00000000..9a35564b --- /dev/null +++ b/frontend/src/shared/ui/adapter/native/NativeInlineMessageAdapter.vue.js @@ -0,0 +1,46 @@ +const __VLS_props = withDefaults(defineProps(), { severity: 'info', dismissible: false }); +const emit = defineEmits(); +const __VLS_defaults = { severity: 'info', dismissible: false }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-inline-message" }, + 'data-severity': (__VLS_ctx.severity), + role: (__VLS_ctx.severity === 'danger' ? 'alert' : 'status'), +}); +/** @type {__VLS_StyleScopedClasses['ks-inline-message']} */ ; +if (__VLS_ctx.title) { + __VLS_asFunctionalElement1(__VLS_intrinsics.strong, __VLS_intrinsics.strong)({}); + (__VLS_ctx.title); +} +__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({}); +(__VLS_ctx.message); +if (__VLS_ctx.dismissible) { + __VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({ + ...{ onClick: (...[$event]) => { + if (!(__VLS_ctx.dismissible)) + throw 0; + return (__VLS_ctx.emit('dismiss')); + // @ts-ignore + [severity, severity, title, title, message, dismissible, emit,]; + } }, + type: "button", + 'aria-label': "메시지 닫기", + }); +} +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/native/NativeMultiSelectAdapter.vue.js b/frontend/src/shared/ui/adapter/native/NativeMultiSelectAdapter.vue.js new file mode 100644 index 00000000..d9365cfc --- /dev/null +++ b/frontend/src/shared/ui/adapter/native/NativeMultiSelectAdapter.vue.js @@ -0,0 +1,58 @@ +const props = withDefaults(defineProps(), { modelValue: () => [] }); +const emit = defineEmits(); +function update(event) { + const selected = Array.from(event.target.selectedOptions).map(x => { + const option = props.options[Number(x.value)]; + return option?.value ?? null; + }); + emit('update:modelValue', selected); +} +const __VLS_defaults = { modelValue: () => [] }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.label, __VLS_intrinsics.label)({ + ...{ class: "ks-field" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-field']} */ ; +if (__VLS_ctx.label) { + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({}); + (__VLS_ctx.label); + if (__VLS_ctx.required) { + __VLS_asFunctionalElement1(__VLS_intrinsics.b, __VLS_intrinsics.b)({ + 'aria-hidden': "true", + }); + } +} +__VLS_asFunctionalElement1(__VLS_intrinsics.select, __VLS_intrinsics.select)({ + ...{ onChange: (__VLS_ctx.update) }, + multiple: true, + disabled: (__VLS_ctx.disabled), + required: (__VLS_ctx.required), +}); +for (const [option, index] of __VLS_vFor((__VLS_ctx.options))) { + __VLS_asFunctionalElement1(__VLS_intrinsics.option, __VLS_intrinsics.option)({ + key: (`${index}:${option.label}`), + value: (index), + disabled: (option.disabled), + selected: (__VLS_ctx.modelValue.includes(option.value)), + }); + (option.label); + // @ts-ignore + [label, label, required, required, update, disabled, options, modelValue,]; +} +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/native/NativeNumberFieldAdapter.vue.js b/frontend/src/shared/ui/adapter/native/NativeNumberFieldAdapter.vue.js new file mode 100644 index 00000000..651c8aff --- /dev/null +++ b/frontend/src/shared/ui/adapter/native/NativeNumberFieldAdapter.vue.js @@ -0,0 +1,43 @@ +const __VLS_props = defineProps(); +const emit = defineEmits(); +function parse(raw) { if (raw.trim() === '') + return null; const value = Number(raw); return Number.isFinite(value) ? value : null; } +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.input)({ + ...{ onInput: (...[$event]) => { + return (__VLS_ctx.emit('update:modelValue', __VLS_ctx.parse($event.target.value))); + // @ts-ignore + [emit, parse,]; + } }, + ...{ onBlur: (...[$event]) => { + return (__VLS_ctx.emit('blur', $event)); + // @ts-ignore + [emit,]; + } }, + id: (__VLS_ctx.inputId), + ...{ class: "ks-native-input" }, + type: "number", + value: (__VLS_ctx.modelValue ?? ''), + disabled: (__VLS_ctx.disabled), + 'aria-invalid': (__VLS_ctx.invalid || undefined), + min: (__VLS_ctx.min), + max: (__VLS_ctx.max), + step: (__VLS_ctx.maxFractionDigits ? 1 / 10 ** __VLS_ctx.maxFractionDigits : 1), +}); +/** @type {__VLS_StyleScopedClasses['ks-native-input']} */ ; +// @ts-ignore +[inputId, modelValue, disabled, invalid, min, max, maxFractionDigits, maxFractionDigits,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/native/NativePaginatorAdapter.vue.js b/frontend/src/shared/ui/adapter/native/NativePaginatorAdapter.vue.js new file mode 100644 index 00000000..03c672c0 --- /dev/null +++ b/frontend/src/shared/ui/adapter/native/NativePaginatorAdapter.vue.js @@ -0,0 +1,66 @@ +const props = withDefaults(defineProps(), { pageSizes: () => [20, 50, 100], disabled: false }); +const emit = defineEmits(); +const pageCount = () => Math.max(1, Math.ceil(props.total / props.pageSize)); +function move(page) { emit('pageChange', { page: Math.min(Math.max(1, page), pageCount()), pageSize: props.pageSize }); } +function size(event) { emit('pageChange', { page: 1, pageSize: Number(event.target.value) }); } +const __VLS_defaults = { pageSizes: () => [20, 50, 100], disabled: false }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.nav, __VLS_intrinsics.nav)({ + ...{ class: "ks-paginator" }, + 'aria-label': "목록 페이지", +}); +/** @type {__VLS_StyleScopedClasses['ks-paginator']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({ + ...{ onClick: (...[$event]) => { + return (__VLS_ctx.move(__VLS_ctx.page - 1)); + // @ts-ignore + [move, page,]; + } }, + type: "button", + disabled: (__VLS_ctx.disabled || __VLS_ctx.page <= 1), +}); +__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({}); +(__VLS_ctx.page); +(__VLS_ctx.pageCount()); +(__VLS_ctx.total); +__VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({ + ...{ onClick: (...[$event]) => { + return (__VLS_ctx.move(__VLS_ctx.page + 1)); + // @ts-ignore + [move, page, page, page, disabled, pageCount, total,]; + } }, + type: "button", + disabled: (__VLS_ctx.disabled || __VLS_ctx.page >= __VLS_ctx.pageCount()), +}); +__VLS_asFunctionalElement1(__VLS_intrinsics.label, __VLS_intrinsics.label)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.select, __VLS_intrinsics.select)({ + ...{ onChange: (__VLS_ctx.size) }, + value: (__VLS_ctx.pageSize), + disabled: (__VLS_ctx.disabled), +}); +for (const [item] of __VLS_vFor((__VLS_ctx.pageSizes))) { + __VLS_asFunctionalElement1(__VLS_intrinsics.option, __VLS_intrinsics.option)({ + key: (item), + value: (item), + }); + (item); + // @ts-ignore + [page, disabled, disabled, pageCount, size, pageSize, pageSizes,]; +} +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/native/NativeSelectAdapter.vue.js b/frontend/src/shared/ui/adapter/native/NativeSelectAdapter.vue.js new file mode 100644 index 00000000..115ca804 --- /dev/null +++ b/frontend/src/shared/ui/adapter/native/NativeSelectAdapter.vue.js @@ -0,0 +1,56 @@ +const props = defineProps(); +const emit = defineEmits(); +function encode(value) { return JSON.stringify(value); } +function decode(raw) { const option = props.options.find(x => encode(x.value) === raw); return option?.value ?? null; } +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.select, __VLS_intrinsics.select)({ + ...{ onChange: (...[$event]) => { + return (__VLS_ctx.emit('update:modelValue', __VLS_ctx.decode($event.target.value))); + // @ts-ignore + [emit, decode,]; + } }, + ...{ onBlur: (...[$event]) => { + return (__VLS_ctx.emit('blur', $event)); + // @ts-ignore + [emit,]; + } }, + id: (__VLS_ctx.inputId), + ...{ class: "ks-native-input" }, + value: (__VLS_ctx.encode(__VLS_ctx.modelValue)), + disabled: (__VLS_ctx.disabled), + 'aria-invalid': (__VLS_ctx.invalid || undefined), +}); +/** @type {__VLS_StyleScopedClasses['ks-native-input']} */ ; +if (__VLS_ctx.placeholder) { + __VLS_asFunctionalElement1(__VLS_intrinsics.option, __VLS_intrinsics.option)({ + value: "", + disabled: true, + }); + (__VLS_ctx.placeholder); +} +for (const [option] of __VLS_vFor((__VLS_ctx.options))) { + __VLS_asFunctionalElement1(__VLS_intrinsics.option, __VLS_intrinsics.option)({ + key: (__VLS_ctx.encode(option.value)), + value: (__VLS_ctx.encode(option.value)), + disabled: (option.disabled), + }); + (option.label); + // @ts-ignore + [inputId, encode, encode, encode, modelValue, disabled, invalid, placeholder, placeholder, options,]; +} +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/native/NativeStatusTagAdapter.vue.js b/frontend/src/shared/ui/adapter/native/NativeStatusTagAdapter.vue.js new file mode 100644 index 00000000..e7e1bf81 --- /dev/null +++ b/frontend/src/shared/ui/adapter/native/NativeStatusTagAdapter.vue.js @@ -0,0 +1,23 @@ +const __VLS_props = withDefaults(defineProps(), { severity: 'info' }); +const __VLS_defaults = { severity: 'info' }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({ + ...{ class: "ks-native-tag" }, + ...{ class: (`is-${__VLS_ctx.severity}`) }, +}); +/** @type {__VLS_StyleScopedClasses['ks-native-tag']} */ ; +(__VLS_ctx.value); +// @ts-ignore +[severity, value,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeProps: {}, + props: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/native/NativeTabsAdapter.vue.js b/frontend/src/shared/ui/adapter/native/NativeTabsAdapter.vue.js new file mode 100644 index 00000000..a49bb4a0 --- /dev/null +++ b/frontend/src/shared/ui/adapter/native/NativeTabsAdapter.vue.js @@ -0,0 +1,58 @@ +const __VLS_props = withDefaults(defineProps(), { ariaLabel: '탭' }); +const emit = defineEmits(); +const __VLS_defaults = { ariaLabel: '탭' }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-tabs" }, + role: "tablist", + 'aria-label': (__VLS_ctx.ariaLabel), +}); +/** @type {__VLS_StyleScopedClasses['ks-tabs']} */ ; +for (const [item] of __VLS_vFor((__VLS_ctx.items))) { + __VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({ + ...{ onClick: (...[$event]) => { + return (__VLS_ctx.emit('update:modelValue', item.id)); + // @ts-ignore + [ariaLabel, items, emit,]; + } }, + key: (item.id), + type: "button", + role: "tab", + 'aria-selected': (__VLS_ctx.modelValue === item.id), + disabled: (item.disabled), + }); + (item.label); + if (item.badge) { + __VLS_asFunctionalElement1(__VLS_intrinsics.small, __VLS_intrinsics.small)({}); + (item.badge); + } + // @ts-ignore + [modelValue,]; +} +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + role: "tabpanel", +}); +var __VLS_0 = { + activeId: (__VLS_ctx.modelValue), +}; +// @ts-ignore +var __VLS_1 = __VLS_0; +// @ts-ignore +[modelValue,]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/adapter/native/NativeTextAreaAdapter.vue.js b/frontend/src/shared/ui/adapter/native/NativeTextAreaAdapter.vue.js new file mode 100644 index 00000000..5620d083 --- /dev/null +++ b/frontend/src/shared/ui/adapter/native/NativeTextAreaAdapter.vue.js @@ -0,0 +1,39 @@ +const __VLS_props = defineProps(); +const emit = defineEmits(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.textarea)({ + ...{ onInput: (...[$event]) => { + return (__VLS_ctx.emit('update:modelValue', $event.target.value)); + // @ts-ignore + [emit,]; + } }, + ...{ onBlur: (...[$event]) => { + return (__VLS_ctx.emit('blur', $event)); + // @ts-ignore + [emit,]; + } }, + id: (__VLS_ctx.inputId), + ...{ class: "ks-native-input" }, + value: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), + 'aria-invalid': (__VLS_ctx.invalid || undefined), + rows: (__VLS_ctx.rows ?? 4), + placeholder: (__VLS_ctx.placeholder), +}); +/** @type {__VLS_StyleScopedClasses['ks-native-input']} */ ; +// @ts-ignore +[inputId, modelValue, disabled, invalid, rows, placeholder,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/native/NativeTextFieldAdapter.vue.js b/frontend/src/shared/ui/adapter/native/NativeTextFieldAdapter.vue.js new file mode 100644 index 00000000..b4f0b6da --- /dev/null +++ b/frontend/src/shared/ui/adapter/native/NativeTextFieldAdapter.vue.js @@ -0,0 +1,39 @@ +const __VLS_props = defineProps(); +const emit = defineEmits(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.input)({ + ...{ onInput: (...[$event]) => { + return (__VLS_ctx.emit('update:modelValue', $event.target.value)); + // @ts-ignore + [emit,]; + } }, + ...{ onBlur: (...[$event]) => { + return (__VLS_ctx.emit('blur', $event)); + // @ts-ignore + [emit,]; + } }, + id: (__VLS_ctx.inputId), + ...{ class: "ks-native-input" }, + type: "text", + value: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), + 'aria-invalid': (__VLS_ctx.invalid || undefined), + placeholder: (__VLS_ctx.placeholder), +}); +/** @type {__VLS_StyleScopedClasses['ks-native-input']} */ ; +// @ts-ignore +[inputId, modelValue, disabled, invalid, placeholder,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/native/index.js b/frontend/src/shared/ui/adapter/native/index.js new file mode 100644 index 00000000..02c664a7 --- /dev/null +++ b/frontend/src/shared/ui/adapter/native/index.js @@ -0,0 +1,22 @@ +import Button from './NativeButtonAdapter.vue'; +import TextField from './NativeTextFieldAdapter.vue'; +import TextArea from './NativeTextAreaAdapter.vue'; +import Select from './NativeSelectAdapter.vue'; +import MultiSelect from './NativeMultiSelectAdapter.vue'; +import Checkbox from './NativeCheckboxAdapter.vue'; +import DateField from './NativeDateFieldAdapter.vue'; +import NumberField from './NativeNumberFieldAdapter.vue'; +import Dialog from './NativeDialogAdapter.vue'; +import StatusTag from './NativeStatusTagAdapter.vue'; +import InlineMessage from './NativeInlineMessageAdapter.vue'; +import Paginator from './NativePaginatorAdapter.vue'; +import Tabs from './NativeTabsAdapter.vue'; +import DataGrid from './NativeDataGridAdapter.vue'; +const capabilities = new Set([ + 'button', 'text-field', 'text-area', 'select', 'multi-select', 'checkbox', 'date-field', 'number-field', + 'dialog', 'status-tag', 'inline-message', 'paginator', 'tabs', 'data-grid' +]); +export const nativeUiAdapter = Object.freeze({ + descriptor: Object.freeze({ id: 'native-accessible', version: '2.0.0', contractVersion: '4.0', vendor: 'HTML platform primitives', capabilities, productionEligible: false, accessibilityBaseline: 'WCAG_2_2_AA_TARGET' }), + components: Object.freeze({ Button, TextField, TextArea, Select, MultiSelect, Checkbox, DateField, NumberField, Dialog, StatusTag, InlineMessage, Paginator, Tabs, DataGrid }) +}); diff --git a/frontend/src/shared/ui/adapter/native/installNativeAdapter.js b/frontend/src/shared/ui/adapter/native/installNativeAdapter.js new file mode 100644 index 00000000..450dfc32 --- /dev/null +++ b/frontend/src/shared/ui/adapter/native/installNativeAdapter.js @@ -0,0 +1,7 @@ +import { installUiAdapter } from '../useUiAdapter'; +import { nativeUiAdapter } from './index'; +import './native.css'; +export const nativeUiProvider = { + id: 'native-accessible', + install(app) { installUiAdapter(app, nativeUiAdapter); } +}; diff --git a/frontend/src/shared/ui/adapter/primevue/AgGridAdapter.vue.js b/frontend/src/shared/ui/adapter/primevue/AgGridAdapter.vue.js new file mode 100644 index 00000000..24c80d79 --- /dev/null +++ b/frontend/src/shared/ui/adapter/primevue/AgGridAdapter.vue.js @@ -0,0 +1,82 @@ +import { computed } from 'vue'; +import { AgGridVue } from 'ag-grid-vue3'; +import { AllCommunityModule, ModuleRegistry, themeQuartz } from 'ag-grid-community'; +ModuleRegistry.registerModules([AllCommunityModule]); +const props = withDefaults(defineProps(), { loading: false, height: '32rem', rowSelection: 'single' }); +const emit = defineEmits(); +const columnDefs = computed(() => props.columns.map(column => ({ + field: column.field, + headerName: column.header, + width: column.width, + minWidth: column.minWidth ?? 120, + sortable: column.sortable ?? true, + filter: column.filterable ?? true, + valueFormatter: column.formatter + ? params => column.formatter?.(params.value, params.data) ?? '' + : undefined +}))); +const rowSelectionOptions = computed(() => { + if (props.rowSelection === 'none') + return undefined; + return props.rowSelection === 'multiple' + ? { mode: 'multiRow' } + : { mode: 'singleRow' }; +}); +function onRowClicked(event) { + if (event.data) + emit('rowSelected', event.data); +} +const __VLS_defaults = { loading: false, height: '32rem', rowSelection: 'single' }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-grid" }, + ...{ style: ({ height: __VLS_ctx.height }) }, + 'aria-busy': (__VLS_ctx.loading), +}); +/** @type {__VLS_StyleScopedClasses['ks-grid']} */ ; +let __VLS_0; +/** @ts-ignore @type { | typeof __VLS_components.AgGridVue} */ +AgGridVue; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onRowClicked': {} }, + ...{ style: {} }, + theme: (__VLS_ctx.themeQuartz), + rowData: (__VLS_ctx.rows), + columnDefs: (__VLS_ctx.columnDefs), + rowSelection: (__VLS_ctx.rowSelectionOptions), + loading: (__VLS_ctx.loading), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onRowClicked': {} }, + ...{ style: {} }, + theme: (__VLS_ctx.themeQuartz), + rowData: (__VLS_ctx.rows), + columnDefs: (__VLS_ctx.columnDefs), + rowSelection: (__VLS_ctx.rowSelectionOptions), + loading: (__VLS_ctx.loading), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.rowClicked} */ + onRowClicked: (__VLS_ctx.onRowClicked), +}; +var __VLS_3; +var __VLS_4; +// @ts-ignore +[height, loading, loading, themeQuartz, rows, columnDefs, rowSelectionOptions, onRowClicked,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/primevue/PrimeButtonAdapter.vue.js b/frontend/src/shared/ui/adapter/primevue/PrimeButtonAdapter.vue.js new file mode 100644 index 00000000..f45c0acc --- /dev/null +++ b/frontend/src/shared/ui/adapter/primevue/PrimeButtonAdapter.vue.js @@ -0,0 +1,66 @@ +import Button from 'primevue/button'; +const __VLS_props = withDefaults(defineProps(), { severity: 'primary', type: 'button', disabled: false, loading: false }); +const __VLS_emit = defineEmits(); +const __VLS_defaults = { severity: 'primary', type: 'button', disabled: false, loading: false }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +/** @type {__VLS_StyleScopedClasses['ks-button']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-button']} */ ; +let __VLS_0; +/** @ts-ignore @type { | typeof __VLS_components.Button | typeof __VLS_components.Button} */ +Button; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onClick': {} }, + ...{ class: "ks-button" }, + label: (__VLS_ctx.label), + severity: (__VLS_ctx.severity), + type: (__VLS_ctx.type), + disabled: (__VLS_ctx.disabled), + loading: (__VLS_ctx.loading), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onClick': {} }, + ...{ class: "ks-button" }, + label: (__VLS_ctx.label), + severity: (__VLS_ctx.severity), + type: (__VLS_ctx.type), + disabled: (__VLS_ctx.disabled), + loading: (__VLS_ctx.loading), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.click} */ + onClick: (...[$event]) => { + return (__VLS_ctx.$emit('activate', $event)); + // @ts-ignore + [label, severity, type, disabled, loading, $emit,]; + }, +}; +var __VLS_7; +/** @type {__VLS_StyleScopedClasses['ks-button']} */ ; +const { default: __VLS_8 } = __VLS_3.slots; +var __VLS_9 = {}; +// @ts-ignore +[]; +var __VLS_3; +var __VLS_4; +// @ts-ignore +var __VLS_10 = __VLS_9; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/adapter/primevue/PrimeCheckboxAdapter.vue.js b/frontend/src/shared/ui/adapter/primevue/PrimeCheckboxAdapter.vue.js new file mode 100644 index 00000000..8a263b84 --- /dev/null +++ b/frontend/src/shared/ui/adapter/primevue/PrimeCheckboxAdapter.vue.js @@ -0,0 +1,53 @@ +import Checkbox from 'primevue/checkbox'; +const __VLS_props = defineProps(); +const __VLS_emit = defineEmits(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +let __VLS_0; +/** @ts-ignore @type { | typeof __VLS_components.Checkbox} */ +Checkbox; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onUpdate:modelValue': {} }, + ...{ class: "ks-checkbox" }, + inputId: (__VLS_ctx.inputId), + modelValue: (__VLS_ctx.modelValue), + binary: true, + disabled: (__VLS_ctx.disabled), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onUpdate:modelValue': {} }, + ...{ class: "ks-checkbox" }, + inputId: (__VLS_ctx.inputId), + modelValue: (__VLS_ctx.modelValue), + binary: true, + disabled: (__VLS_ctx.disabled), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.'update:modelValue'} */ + 'onUpdate:modelValue': (...[$event]) => { + return (__VLS_ctx.$emit('update:modelValue', Boolean($event))); + // @ts-ignore + [inputId, modelValue, disabled, $emit,]; + }, +}; +var __VLS_7; +/** @type {__VLS_StyleScopedClasses['ks-checkbox']} */ ; +var __VLS_3; +var __VLS_4; +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/primevue/PrimeDateFieldAdapter.vue.js b/frontend/src/shared/ui/adapter/primevue/PrimeDateFieldAdapter.vue.js new file mode 100644 index 00000000..57dc321e --- /dev/null +++ b/frontend/src/shared/ui/adapter/primevue/PrimeDateFieldAdapter.vue.js @@ -0,0 +1,80 @@ +import DatePicker from 'primevue/datepicker'; +import { computed } from 'vue'; +const props = defineProps(); +const emit = defineEmits(); +const dateValue = computed(() => { + if (props.modelValue instanceof Date) + return props.modelValue; + if (typeof props.modelValue === 'string') + return new Date(props.modelValue); + return null; +}); +const handleDateChange = (value) => { + if (value instanceof Date) + emit('update:modelValue', value); + else if (value === null || value === undefined) + emit('update:modelValue', null); + else + emit('update:modelValue', value[0] ?? null); +}; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +let __VLS_0; +/** @ts-ignore @type { | typeof __VLS_components.DatePicker} */ +DatePicker; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + inputId: (__VLS_ctx.inputId), + modelValue: (__VLS_ctx.dateValue), + disabled: (__VLS_ctx.disabled), + invalid: (__VLS_ctx.invalid), + minDate: (__VLS_ctx.min), + maxDate: (__VLS_ctx.max), + dateFormat: "yy-mm-dd", + showIcon: true, +})); +const __VLS_2 = __VLS_1({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + inputId: (__VLS_ctx.inputId), + modelValue: (__VLS_ctx.dateValue), + disabled: (__VLS_ctx.disabled), + invalid: (__VLS_ctx.invalid), + minDate: (__VLS_ctx.min), + maxDate: (__VLS_ctx.max), + dateFormat: "yy-mm-dd", + showIcon: true, +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.'update:modelValue'} */ + 'onUpdate:modelValue': (__VLS_ctx.handleDateChange), +}; +const __VLS_7 = { + /** @type {typeof __VLS_5.blur} */ + onBlur: (...[$event]) => { + return (__VLS_ctx.emit('blur', $event)); + // @ts-ignore + [inputId, dateValue, disabled, invalid, min, max, handleDateChange, emit,]; + }, +}; +var __VLS_8; +var __VLS_3; +var __VLS_4; +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/primevue/PrimeDialogAdapter.vue.js b/frontend/src/shared/ui/adapter/primevue/PrimeDialogAdapter.vue.js new file mode 100644 index 00000000..3f8c2305 --- /dev/null +++ b/frontend/src/shared/ui/adapter/primevue/PrimeDialogAdapter.vue.js @@ -0,0 +1,66 @@ +import Dialog from 'primevue/dialog'; +const __VLS_props = defineProps(); +const __VLS_emit = defineEmits(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +let __VLS_0; +/** @ts-ignore @type { | typeof __VLS_components.Dialog | typeof __VLS_components.Dialog} */ +Dialog; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onUpdate:visible': {} }, + ...{ class: "ks-dialog" }, + visible: (__VLS_ctx.visible), + header: (__VLS_ctx.title), + modal: (__VLS_ctx.modal ?? true), + closable: (__VLS_ctx.closable ?? true), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onUpdate:visible': {} }, + ...{ class: "ks-dialog" }, + visible: (__VLS_ctx.visible), + header: (__VLS_ctx.title), + modal: (__VLS_ctx.modal ?? true), + closable: (__VLS_ctx.closable ?? true), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.'update:visible'} */ + 'onUpdate:visible': (...[$event]) => { + return (__VLS_ctx.$emit('update:visible', $event)); + // @ts-ignore + [visible, title, modal, closable, $emit,]; + }, +}; +var __VLS_7; +/** @type {__VLS_StyleScopedClasses['ks-dialog']} */ ; +const { default: __VLS_8 } = __VLS_3.slots; +var __VLS_9 = {}; +{ + const { footer: __VLS_11 } = __VLS_3.slots; + var __VLS_12 = {}; + // @ts-ignore + []; +} +// @ts-ignore +[]; +var __VLS_3; +var __VLS_4; +// @ts-ignore +var __VLS_10 = __VLS_9, __VLS_13 = __VLS_12; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/adapter/primevue/PrimeInlineMessageAdapter.vue.js b/frontend/src/shared/ui/adapter/primevue/PrimeInlineMessageAdapter.vue.js new file mode 100644 index 00000000..690e2094 --- /dev/null +++ b/frontend/src/shared/ui/adapter/primevue/PrimeInlineMessageAdapter.vue.js @@ -0,0 +1,57 @@ +import Message from 'primevue/message'; +const __VLS_props = withDefaults(defineProps(), { severity: 'info', dismissible: false }); +const emit = defineEmits(); +const map = { primary: 'info', secondary: 'secondary', success: 'success', info: 'info', warning: 'warn', danger: 'error' }; +const __VLS_defaults = { severity: 'info', dismissible: false }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +let __VLS_0; +/** @ts-ignore @type { | typeof __VLS_components.Message | typeof __VLS_components.Message} */ +Message; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onClose': {} }, + severity: (__VLS_ctx.map[__VLS_ctx.severity]), + closable: (__VLS_ctx.dismissible), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onClose': {} }, + severity: (__VLS_ctx.map[__VLS_ctx.severity]), + closable: (__VLS_ctx.dismissible), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.close} */ + onClose: (...[$event]) => { + return (__VLS_ctx.emit('dismiss')); + // @ts-ignore + [map, severity, dismissible, emit,]; + }, +}; +var __VLS_7; +const { default: __VLS_8 } = __VLS_3.slots; +if (__VLS_ctx.title) { + __VLS_asFunctionalElement1(__VLS_intrinsics.strong, __VLS_intrinsics.strong)({}); + (__VLS_ctx.title); +} +(__VLS_ctx.message); +// @ts-ignore +[title, title, message,]; +var __VLS_3; +var __VLS_4; +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/primevue/PrimeMultiSelectAdapter.vue.js b/frontend/src/shared/ui/adapter/primevue/PrimeMultiSelectAdapter.vue.js new file mode 100644 index 00000000..f456e969 --- /dev/null +++ b/frontend/src/shared/ui/adapter/primevue/PrimeMultiSelectAdapter.vue.js @@ -0,0 +1,68 @@ +import MultiSelect from 'primevue/multiselect'; +const __VLS_props = withDefaults(defineProps(), { modelValue: () => [] }); +const emit = defineEmits(); +const __VLS_defaults = { modelValue: () => [] }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.label, __VLS_intrinsics.label)({ + ...{ class: "ks-field" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-field']} */ ; +if (__VLS_ctx.label) { + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({}); + (__VLS_ctx.label); + if (__VLS_ctx.required) { + __VLS_asFunctionalElement1(__VLS_intrinsics.b, __VLS_intrinsics.b)({ + 'aria-hidden': "true", + }); + } +} +let __VLS_0; +/** @ts-ignore @type { | typeof __VLS_components.MultiSelect} */ +MultiSelect; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onUpdate:modelValue': {} }, + modelValue: (__VLS_ctx.modelValue), + options: (__VLS_ctx.options), + optionLabel: "label", + optionValue: "value", + optionDisabled: "disabled", + disabled: (__VLS_ctx.disabled), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onUpdate:modelValue': {} }, + modelValue: (__VLS_ctx.modelValue), + options: (__VLS_ctx.options), + optionLabel: "label", + optionValue: "value", + optionDisabled: "disabled", + disabled: (__VLS_ctx.disabled), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.'update:modelValue'} */ + 'onUpdate:modelValue': (...[$event]) => { + return (__VLS_ctx.emit('update:modelValue', $event)); + // @ts-ignore + [label, label, required, modelValue, options, disabled, emit,]; + }, +}; +var __VLS_3; +var __VLS_4; +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/primevue/PrimeNumberFieldAdapter.vue.js b/frontend/src/shared/ui/adapter/primevue/PrimeNumberFieldAdapter.vue.js new file mode 100644 index 00000000..72d49607 --- /dev/null +++ b/frontend/src/shared/ui/adapter/primevue/PrimeNumberFieldAdapter.vue.js @@ -0,0 +1,68 @@ +import InputNumber from 'primevue/inputnumber'; +const __VLS_props = defineProps(); +const emit = defineEmits(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +let __VLS_0; +/** @ts-ignore @type { | typeof __VLS_components.InputNumber} */ +InputNumber; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + inputId: (__VLS_ctx.inputId), + modelValue: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), + invalid: (__VLS_ctx.invalid), + min: (__VLS_ctx.min), + max: (__VLS_ctx.max), + minFractionDigits: (__VLS_ctx.minFractionDigits), + maxFractionDigits: (__VLS_ctx.maxFractionDigits), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + inputId: (__VLS_ctx.inputId), + modelValue: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), + invalid: (__VLS_ctx.invalid), + min: (__VLS_ctx.min), + max: (__VLS_ctx.max), + minFractionDigits: (__VLS_ctx.minFractionDigits), + maxFractionDigits: (__VLS_ctx.maxFractionDigits), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.'update:modelValue'} */ + 'onUpdate:modelValue': (...[$event]) => { + return (__VLS_ctx.emit('update:modelValue', $event)); + // @ts-ignore + [inputId, modelValue, disabled, invalid, min, max, minFractionDigits, maxFractionDigits, emit,]; + }, +}; +const __VLS_7 = { + /** @type {typeof __VLS_5.blur} */ + onBlur: (...[$event]) => { + return (__VLS_ctx.emit('blur', $event)); + // @ts-ignore + [emit,]; + }, +}; +var __VLS_8; +var __VLS_3; +var __VLS_4; +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/primevue/PrimePaginatorAdapter.vue.js b/frontend/src/shared/ui/adapter/primevue/PrimePaginatorAdapter.vue.js new file mode 100644 index 00000000..fcc55d17 --- /dev/null +++ b/frontend/src/shared/ui/adapter/primevue/PrimePaginatorAdapter.vue.js @@ -0,0 +1,54 @@ +import Paginator from 'primevue/paginator'; +const __VLS_props = withDefaults(defineProps(), { pageSizes: () => [20, 50, 100], disabled: false }); +const emit = defineEmits(); +const __VLS_defaults = { pageSizes: () => [20, 50, 100], disabled: false }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +let __VLS_0; +/** @ts-ignore @type { | typeof __VLS_components.Paginator} */ +Paginator; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onPage': {} }, + first: ((__VLS_ctx.page - 1) * __VLS_ctx.pageSize), + rows: (__VLS_ctx.pageSize), + totalRecords: (__VLS_ctx.total), + rowsPerPageOptions: (__VLS_ctx.pageSizes), + disabled: (__VLS_ctx.disabled), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onPage': {} }, + first: ((__VLS_ctx.page - 1) * __VLS_ctx.pageSize), + rows: (__VLS_ctx.pageSize), + totalRecords: (__VLS_ctx.total), + rowsPerPageOptions: (__VLS_ctx.pageSizes), + disabled: (__VLS_ctx.disabled), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.page} */ + onPage: (...[$event]) => { + return (__VLS_ctx.emit('pageChange', { page: $event.page + 1, pageSize: $event.rows })); + // @ts-ignore + [page, pageSize, pageSize, total, pageSizes, disabled, emit,]; + }, +}; +var __VLS_7; +var __VLS_3; +var __VLS_4; +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/primevue/PrimeSelectAdapter.vue.js b/frontend/src/shared/ui/adapter/primevue/PrimeSelectAdapter.vue.js new file mode 100644 index 00000000..5d50fad5 --- /dev/null +++ b/frontend/src/shared/ui/adapter/primevue/PrimeSelectAdapter.vue.js @@ -0,0 +1,73 @@ +import Select from 'primevue/select'; +const __VLS_props = defineProps(); +const __VLS_emit = defineEmits(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +let __VLS_0; +/** @ts-ignore @type { | typeof __VLS_components.Select} */ +Select; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + ...{ class: "ks-select" }, + inputId: (__VLS_ctx.inputId), + modelValue: (__VLS_ctx.modelValue), + options: (__VLS_ctx.options), + optionLabel: "label", + optionValue: "value", + optionDisabled: "disabled", + disabled: (__VLS_ctx.disabled), + invalid: (__VLS_ctx.invalid), + placeholder: (__VLS_ctx.placeholder), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + ...{ class: "ks-select" }, + inputId: (__VLS_ctx.inputId), + modelValue: (__VLS_ctx.modelValue), + options: (__VLS_ctx.options), + optionLabel: "label", + optionValue: "value", + optionDisabled: "disabled", + disabled: (__VLS_ctx.disabled), + invalid: (__VLS_ctx.invalid), + placeholder: (__VLS_ctx.placeholder), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.'update:modelValue'} */ + 'onUpdate:modelValue': (...[$event]) => { + return (__VLS_ctx.$emit('update:modelValue', $event)); + // @ts-ignore + [inputId, modelValue, options, disabled, invalid, placeholder, $emit,]; + }, +}; +const __VLS_7 = { + /** @type {typeof __VLS_5.blur} */ + onBlur: (...[$event]) => { + return (__VLS_ctx.$emit('blur', $event)); + // @ts-ignore + [$emit,]; + }, +}; +var __VLS_8; +/** @type {__VLS_StyleScopedClasses['ks-select']} */ ; +var __VLS_3; +var __VLS_4; +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/primevue/PrimeStatusTagAdapter.vue.js b/frontend/src/shared/ui/adapter/primevue/PrimeStatusTagAdapter.vue.js new file mode 100644 index 00000000..5006ca30 --- /dev/null +++ b/frontend/src/shared/ui/adapter/primevue/PrimeStatusTagAdapter.vue.js @@ -0,0 +1,43 @@ +import Tag from 'primevue/tag'; +const __VLS_props = defineProps(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +let __VLS_0; +/** @ts-ignore @type { | typeof __VLS_components.Tag | typeof __VLS_components.Tag} */ +Tag; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ class: "ks-status-tag" }, + severity: (__VLS_ctx.severity ?? 'info'), +})); +const __VLS_2 = __VLS_1({ + ...{ class: "ks-status-tag" }, + severity: (__VLS_ctx.severity ?? 'info'), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +/** @type {__VLS_StyleScopedClasses['ks-status-tag']} */ ; +const { default: __VLS_6 } = __VLS_3.slots; +if (__VLS_ctx.iconLabel) { + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({ + 'aria-hidden': "true", + }); + (__VLS_ctx.iconLabel); +} +__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({}); +(__VLS_ctx.value); +// @ts-ignore +[severity, iconLabel, iconLabel, value,]; +var __VLS_3; +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/primevue/PrimeTabsAdapter.vue.js b/frontend/src/shared/ui/adapter/primevue/PrimeTabsAdapter.vue.js new file mode 100644 index 00000000..a49bb4a0 --- /dev/null +++ b/frontend/src/shared/ui/adapter/primevue/PrimeTabsAdapter.vue.js @@ -0,0 +1,58 @@ +const __VLS_props = withDefaults(defineProps(), { ariaLabel: '탭' }); +const emit = defineEmits(); +const __VLS_defaults = { ariaLabel: '탭' }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-tabs" }, + role: "tablist", + 'aria-label': (__VLS_ctx.ariaLabel), +}); +/** @type {__VLS_StyleScopedClasses['ks-tabs']} */ ; +for (const [item] of __VLS_vFor((__VLS_ctx.items))) { + __VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({ + ...{ onClick: (...[$event]) => { + return (__VLS_ctx.emit('update:modelValue', item.id)); + // @ts-ignore + [ariaLabel, items, emit,]; + } }, + key: (item.id), + type: "button", + role: "tab", + 'aria-selected': (__VLS_ctx.modelValue === item.id), + disabled: (item.disabled), + }); + (item.label); + if (item.badge) { + __VLS_asFunctionalElement1(__VLS_intrinsics.small, __VLS_intrinsics.small)({}); + (item.badge); + } + // @ts-ignore + [modelValue,]; +} +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + role: "tabpanel", +}); +var __VLS_0 = { + activeId: (__VLS_ctx.modelValue), +}; +// @ts-ignore +var __VLS_1 = __VLS_0; +// @ts-ignore +[modelValue,]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/adapter/primevue/PrimeTextAreaAdapter.vue.js b/frontend/src/shared/ui/adapter/primevue/PrimeTextAreaAdapter.vue.js new file mode 100644 index 00000000..a0124228 --- /dev/null +++ b/frontend/src/shared/ui/adapter/primevue/PrimeTextAreaAdapter.vue.js @@ -0,0 +1,67 @@ +import Textarea from 'primevue/textarea'; +const __VLS_props = defineProps(); +const __VLS_emit = defineEmits(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +let __VLS_0; +/** @ts-ignore @type { | typeof __VLS_components.Textarea} */ +Textarea; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + ...{ class: "ks-textarea" }, + id: (__VLS_ctx.inputId), + modelValue: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), + invalid: (__VLS_ctx.invalid), + rows: (__VLS_ctx.rows ?? 4), + placeholder: (__VLS_ctx.placeholder), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + ...{ class: "ks-textarea" }, + id: (__VLS_ctx.inputId), + modelValue: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), + invalid: (__VLS_ctx.invalid), + rows: (__VLS_ctx.rows ?? 4), + placeholder: (__VLS_ctx.placeholder), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.'update:modelValue'} */ + 'onUpdate:modelValue': (...[$event]) => { + return (__VLS_ctx.$emit('update:modelValue', String($event ?? ''))); + // @ts-ignore + [inputId, modelValue, disabled, invalid, rows, placeholder, $emit,]; + }, +}; +const __VLS_7 = { + /** @type {typeof __VLS_5.blur} */ + onBlur: (...[$event]) => { + return (__VLS_ctx.$emit('blur', $event)); + // @ts-ignore + [$emit,]; + }, +}; +var __VLS_8; +/** @type {__VLS_StyleScopedClasses['ks-textarea']} */ ; +var __VLS_3; +var __VLS_4; +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/primevue/PrimeTextFieldAdapter.vue.js b/frontend/src/shared/ui/adapter/primevue/PrimeTextFieldAdapter.vue.js new file mode 100644 index 00000000..b25961ac --- /dev/null +++ b/frontend/src/shared/ui/adapter/primevue/PrimeTextFieldAdapter.vue.js @@ -0,0 +1,66 @@ +import InputText from 'primevue/inputtext'; +const __VLS_props = defineProps(); +const __VLS_emit = defineEmits(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +/** @type {__VLS_StyleScopedClasses['ks-input']} */ ; +let __VLS_0; +/** @ts-ignore @type { | typeof __VLS_components.InputText} */ +InputText; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + ...{ class: "ks-input" }, + id: (__VLS_ctx.inputId), + modelValue: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), + invalid: (__VLS_ctx.invalid), + placeholder: (__VLS_ctx.placeholder), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + ...{ class: "ks-input" }, + id: (__VLS_ctx.inputId), + modelValue: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), + invalid: (__VLS_ctx.invalid), + placeholder: (__VLS_ctx.placeholder), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.'update:modelValue'} */ + 'onUpdate:modelValue': (...[$event]) => { + return (__VLS_ctx.$emit('update:modelValue', String($event ?? ''))); + // @ts-ignore + [inputId, modelValue, disabled, invalid, placeholder, $emit,]; + }, +}; +const __VLS_7 = { + /** @type {typeof __VLS_5.blur} */ + onBlur: (...[$event]) => { + return (__VLS_ctx.$emit('blur', $event)); + // @ts-ignore + [$emit,]; + }, +}; +var __VLS_8; +/** @type {__VLS_StyleScopedClasses['ks-input']} */ ; +var __VLS_3; +var __VLS_4; +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/adapter/primevue/index.js b/frontend/src/shared/ui/adapter/primevue/index.js new file mode 100644 index 00000000..9b8575f5 --- /dev/null +++ b/frontend/src/shared/ui/adapter/primevue/index.js @@ -0,0 +1,22 @@ +import Button from './PrimeButtonAdapter.vue'; +import TextField from './PrimeTextFieldAdapter.vue'; +import TextArea from './PrimeTextAreaAdapter.vue'; +import Select from './PrimeSelectAdapter.vue'; +import MultiSelect from './PrimeMultiSelectAdapter.vue'; +import Checkbox from './PrimeCheckboxAdapter.vue'; +import DateField from './PrimeDateFieldAdapter.vue'; +import NumberField from './PrimeNumberFieldAdapter.vue'; +import Dialog from './PrimeDialogAdapter.vue'; +import StatusTag from './PrimeStatusTagAdapter.vue'; +import InlineMessage from './PrimeInlineMessageAdapter.vue'; +import Paginator from './PrimePaginatorAdapter.vue'; +import Tabs from './PrimeTabsAdapter.vue'; +import DataGrid from './AgGridAdapter.vue'; +const capabilities = new Set([ + 'button', 'text-field', 'text-area', 'select', 'multi-select', 'checkbox', 'date-field', 'number-field', + 'dialog', 'status-tag', 'inline-message', 'paginator', 'tabs', 'data-grid' +]); +export const primeVueUiAdapter = Object.freeze({ + descriptor: Object.freeze({ id: 'primevue-aggrid', version: '4.x+34.x', contractVersion: '4.0', vendor: 'PrimeVue + AG Grid Community', capabilities, productionEligible: true, accessibilityBaseline: 'WCAG_2_2_AA_TARGET' }), + components: Object.freeze({ Button, TextField, TextArea, Select, MultiSelect, Checkbox, DateField, NumberField, Dialog, StatusTag, InlineMessage, Paginator, Tabs, DataGrid }) +}); diff --git a/frontend/src/shared/ui/adapter/primevue/installPrimeVueAdapter.js b/frontend/src/shared/ui/adapter/primevue/installPrimeVueAdapter.js new file mode 100644 index 00000000..2c1dc0d5 --- /dev/null +++ b/frontend/src/shared/ui/adapter/primevue/installPrimeVueAdapter.js @@ -0,0 +1,14 @@ +import PrimeVue from 'primevue/config'; +import { installUiAdapter } from '../useUiAdapter'; +import { primeVueUiAdapter } from './index'; +import './adapter.css'; +export const primeVueUiProvider = { + id: 'primevue-aggrid', + install(app) { + app.use(PrimeVue, { unstyled: true }); + installUiAdapter(app, primeVueUiAdapter); + } +}; +export function installPrimeVueAdapter(app) { + primeVueUiProvider.install(app); +} diff --git a/frontend/src/shared/ui/adapter/tests/uiAdapter.contract.spec.js b/frontend/src/shared/ui/adapter/tests/uiAdapter.contract.spec.js new file mode 100644 index 00000000..9a978df5 --- /dev/null +++ b/frontend/src/shared/ui/adapter/tests/uiAdapter.contract.spec.js @@ -0,0 +1,12 @@ +import { describe, expect, it } from 'vitest'; +import { assertUiAdapterContract, requiredUiAdapterCapabilities } from '../contracts'; +import { nativeUiAdapter } from '../native'; +import { primeVueUiAdapter } from '../primevue'; +describe.each([nativeUiAdapter, primeVueUiAdapter])('UI adapter $descriptor.id', adapter => { + it('implements the complete v4 normalized contract', () => { + expect(() => assertUiAdapterContract(adapter)).not.toThrow(); + expect(adapter.descriptor.contractVersion).toBe('4.0'); + expect(adapter.descriptor.capabilities.size).toBe(requiredUiAdapterCapabilities.length); + expect(adapter.descriptor.accessibilityBaseline).toBe('WCAG_2_2_AA_TARGET'); + }); +}); diff --git a/frontend/src/shared/ui/adapter/useUiAdapter.js b/frontend/src/shared/ui/adapter/useUiAdapter.js new file mode 100644 index 00000000..6fb7ac38 --- /dev/null +++ b/frontend/src/shared/ui/adapter/useUiAdapter.js @@ -0,0 +1,13 @@ +import { inject } from 'vue'; +import { assertUiAdapterContract, uiAdapterKey } from './contracts'; +export function installUiAdapter(app, adapter) { + assertUiAdapterContract(adapter); + app.provide(uiAdapterKey, adapter); +} +export function useUiAdapter() { + const adapter = inject(uiAdapterKey); + if (!adapter) { + throw new Error('UI adapter is not installed. Install a validated provider during app bootstrap.'); + } + return adapter; +} diff --git a/frontend/src/shared/ui/components/FieldShell.vue.js b/frontend/src/shared/ui/components/FieldShell.vue.js new file mode 100644 index 00000000..e0c0fd78 --- /dev/null +++ b/frontend/src/shared/ui/components/FieldShell.vue.js @@ -0,0 +1,51 @@ +import { computed, useId } from 'vue'; +const props = defineProps(); +const generatedId = useId(); +const resolvedId = computed(() => props.inputId ?? `ks-field-${generatedId}`); +const messageId = computed(() => props.error || props.help ? `${resolvedId.value}-message` : undefined); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-field-shell" }, + 'data-invalid': (Boolean(__VLS_ctx.error) || undefined), +}); +/** @type {__VLS_StyleScopedClasses['ks-field-shell']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.label, __VLS_intrinsics.label)({ + for: (__VLS_ctx.resolvedId), +}); +(__VLS_ctx.label); +if (__VLS_ctx.required) { + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({ + 'aria-hidden': "true", + }); +} +var __VLS_0 = { + inputId: (__VLS_ctx.resolvedId), + describedBy: (__VLS_ctx.messageId), + invalid: (Boolean(__VLS_ctx.error)), +}; +if (__VLS_ctx.error || __VLS_ctx.help) { + __VLS_asFunctionalElement1(__VLS_intrinsics.small, __VLS_intrinsics.small)({ + id: (__VLS_ctx.messageId), + ...{ class: ({ 'ks-danger-text': __VLS_ctx.error }) }, + role: (__VLS_ctx.error ? 'alert' : undefined), + }); + /** @type {__VLS_StyleScopedClasses['ks-danger-text']} */ ; + (__VLS_ctx.error ?? __VLS_ctx.help); +} +// @ts-ignore +var __VLS_1 = __VLS_0; +// @ts-ignore +[error, error, error, error, error, error, resolvedId, resolvedId, label, required, messageId, messageId, help, help,]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/components/KsButton.vue.js b/frontend/src/shared/ui/components/KsButton.vue.js new file mode 100644 index 00000000..63efec72 --- /dev/null +++ b/frontend/src/shared/ui/components/KsButton.vue.js @@ -0,0 +1,56 @@ +import { useUiAdapter } from '../adapter/useUiAdapter'; +const __VLS_props = withDefaults(defineProps(), { + severity: 'primary', type: 'button', disabled: false, loading: false +}); +const emit = defineEmits(); +const adapter = useUiAdapter(); +const __VLS_defaults = { + severity: 'primary', type: 'button', disabled: false, loading: false +}; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = (__VLS_ctx.adapter.components.Button); +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onActivate': {} }, + ...(__VLS_ctx.$props), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onActivate': {} }, + ...(__VLS_ctx.$props), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.activate} */ + onActivate: (...[$event]) => { + return (__VLS_ctx.emit('click', $event)); + // @ts-ignore + [adapter, $props, emit,]; + }, +}; +var __VLS_7; +const { default: __VLS_8 } = __VLS_3.slots; +var __VLS_9 = {}; +// @ts-ignore +[]; +var __VLS_3; +var __VLS_4; +// @ts-ignore +var __VLS_10 = __VLS_9; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/components/KsCheckbox.vue.js b/frontend/src/shared/ui/components/KsCheckbox.vue.js new file mode 100644 index 00000000..8c296248 --- /dev/null +++ b/frontend/src/shared/ui/components/KsCheckbox.vue.js @@ -0,0 +1,56 @@ +import { computed, useId } from 'vue'; +import { useUiAdapter } from '../adapter/useUiAdapter'; +const props = defineProps(); +const emit = defineEmits(); +const adapter = useUiAdapter(); +const generatedId = useId(); +const resolvedId = computed(() => props.inputId ?? `ks-check-${generatedId}`); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.label, __VLS_intrinsics.label)({ + ...{ class: "ks-check" }, + for: (__VLS_ctx.resolvedId), +}); +/** @type {__VLS_StyleScopedClasses['ks-check']} */ ; +const __VLS_0 = (__VLS_ctx.adapter.components.Checkbox); +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onUpdate:modelValue': {} }, + inputId: (__VLS_ctx.resolvedId), + modelValue: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onUpdate:modelValue': {} }, + inputId: (__VLS_ctx.resolvedId), + modelValue: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.'update:modelValue'} */ + 'onUpdate:modelValue': (...[$event]) => { + return (__VLS_ctx.emit('update:modelValue', $event)); + // @ts-ignore + [resolvedId, resolvedId, adapter, modelValue, disabled, emit,]; + }, +}; +var __VLS_3; +var __VLS_4; +__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({}); +(__VLS_ctx.label); +// @ts-ignore +[label,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/components/KsCommandBar.vue.js b/frontend/src/shared/ui/components/KsCommandBar.vue.js new file mode 100644 index 00000000..9d84ea5f --- /dev/null +++ b/frontend/src/shared/ui/components/KsCommandBar.vue.js @@ -0,0 +1,58 @@ +import KsButton from './KsButton.vue'; +const __VLS_props = defineProps(); +const emit = defineEmits(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.nav, __VLS_intrinsics.nav)({ + ...{ class: "ks-command-bar" }, + 'aria-label': (__VLS_ctx.ariaLabel ?? 'Page actions'), +}); +/** @type {__VLS_StyleScopedClasses['ks-command-bar']} */ ; +for (const [action] of __VLS_vFor((__VLS_ctx.actions))) { + const __VLS_0 = KsButton; + // @ts-ignore + const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onClick': {} }, + key: (action.id), + label: (action.label), + severity: (action.severity), + disabled: (action.disabled), + loading: (action.busy), + })); + const __VLS_2 = __VLS_1({ + ...{ 'onClick': {} }, + key: (action.id), + label: (action.label), + severity: (action.severity), + disabled: (action.disabled), + loading: (action.busy), + }, ...__VLS_functionalComponentArgsRest(__VLS_1)); + let __VLS_5; + const __VLS_6 = { + /** @type {typeof __VLS_5.click} */ + onClick: (...[$event]) => { + return (__VLS_ctx.emit('execute', action.id)); + // @ts-ignore + [ariaLabel, actions, emit,]; + }, + }; + var __VLS_3; + var __VLS_4; + // @ts-ignore + []; +} +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/components/KsDataContextHeader.vue.js b/frontend/src/shared/ui/components/KsDataContextHeader.vue.js new file mode 100644 index 00000000..20f444d8 --- /dev/null +++ b/frontend/src/shared/ui/components/KsDataContextHeader.vue.js @@ -0,0 +1,68 @@ +import { computed } from 'vue'; +import EvidenceVersionSet from '../EvidenceVersionSet.vue'; +const props = defineProps(); +const versionSet = computed(() => ({ + datasetId: props.datasetId, + dataHash: props.dataHash, + modelVersion: props.modelVersion, + configVersion: props.configVersion, + codeSha: props.codeSha, + contractVersion: props.contractVersion +})); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +/** @type {__VLS_StyleScopedClasses['ks-data-context']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.header, __VLS_intrinsics.header)({ + ...{ class: "ks-data-context" }, + 'data-stale': (__VLS_ctx.stale || undefined), +}); +/** @type {__VLS_StyleScopedClasses['ks-data-context']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.h1, __VLS_intrinsics.h1)({}); +(__VLS_ctx.title); +__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({}); +(__VLS_ctx.asOf); +if (__VLS_ctx.stale) { + __VLS_asFunctionalElement1(__VLS_intrinsics.strong, __VLS_intrinsics.strong)({}); +} +const __VLS_0 = EvidenceVersionSet; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + value: (__VLS_ctx.versionSet), + compact: true, +})); +const __VLS_2 = __VLS_1({ + value: (__VLS_ctx.versionSet), + compact: true, +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +if (__VLS_ctx.projectionVersion || __VLS_ctx.watermark || __VLS_ctx.correlationId) { + __VLS_asFunctionalElement1(__VLS_intrinsics.dl, __VLS_intrinsics.dl)({}); + if (__VLS_ctx.projectionVersion) { + __VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); + (__VLS_ctx.projectionVersion); + } + if (__VLS_ctx.watermark) { + __VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); + (__VLS_ctx.watermark); + } + if (__VLS_ctx.correlationId) { + __VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({}); + __VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({}); + (__VLS_ctx.correlationId); + } +} +// @ts-ignore +[stale, stale, title, asOf, versionSet, projectionVersion, projectionVersion, projectionVersion, watermark, watermark, watermark, correlationId, correlationId, correlationId,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/components/KsDataGrid.vue.js b/frontend/src/shared/ui/components/KsDataGrid.vue.js new file mode 100644 index 00000000..049c2aa6 --- /dev/null +++ b/frontend/src/shared/ui/components/KsDataGrid.vue.js @@ -0,0 +1,45 @@ +import { useUiAdapter } from '../adapter/useUiAdapter'; +const __VLS_props = withDefaults(defineProps(), { loading: false, height: '32rem', rowSelection: 'single' }); +const emit = defineEmits(); +const adapter = useUiAdapter(); +const __VLS_defaults = { loading: false, height: '32rem', rowSelection: 'single' }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = (__VLS_ctx.adapter.components.DataGrid); +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onRowSelected': {} }, + ...(__VLS_ctx.$props), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onRowSelected': {} }, + ...(__VLS_ctx.$props), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.rowSelected} */ + onRowSelected: (...[$event]) => { + return (__VLS_ctx.emit('rowSelected', $event)); + // @ts-ignore + [adapter, $props, emit,]; + }, +}; +var __VLS_7; +var __VLS_3; +var __VLS_4; +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/components/KsDateField.vue.js b/frontend/src/shared/ui/components/KsDateField.vue.js new file mode 100644 index 00000000..930bf768 --- /dev/null +++ b/frontend/src/shared/ui/components/KsDateField.vue.js @@ -0,0 +1,88 @@ +import { computed, useId } from 'vue'; +import { useUiAdapter } from '../adapter/useUiAdapter'; +const props = defineProps(); +const emit = defineEmits(); +const adapter = useUiAdapter(); +const generatedId = useId(); +const resolvedId = computed(() => props.inputId ?? `ks-date-${generatedId}`); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-field" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-field']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.label, __VLS_intrinsics.label)({ + for: (__VLS_ctx.resolvedId), +}); +(__VLS_ctx.label); +if (__VLS_ctx.required) { + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({ + 'aria-hidden': "true", + }); +} +const __VLS_0 = (__VLS_ctx.adapter.components.DateField); +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + inputId: (__VLS_ctx.resolvedId), + modelValue: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), + invalid: (Boolean(__VLS_ctx.error)), + min: (__VLS_ctx.min), + max: (__VLS_ctx.max), + 'aria-describedby': (__VLS_ctx.error || __VLS_ctx.help ? `${__VLS_ctx.resolvedId}-message` : undefined), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + inputId: (__VLS_ctx.resolvedId), + modelValue: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), + invalid: (Boolean(__VLS_ctx.error)), + min: (__VLS_ctx.min), + max: (__VLS_ctx.max), + 'aria-describedby': (__VLS_ctx.error || __VLS_ctx.help ? `${__VLS_ctx.resolvedId}-message` : undefined), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.'update:modelValue'} */ + 'onUpdate:modelValue': (...[$event]) => { + return (__VLS_ctx.emit('update:modelValue', $event)); + // @ts-ignore + [resolvedId, resolvedId, resolvedId, label, required, adapter, modelValue, disabled, error, error, min, max, help, emit,]; + }, +}; +const __VLS_7 = { + /** @type {typeof __VLS_5.blur} */ + onBlur: (...[$event]) => { + return (__VLS_ctx.emit('blur', $event)); + // @ts-ignore + [emit,]; + }, +}; +var __VLS_3; +var __VLS_4; +if (__VLS_ctx.error || __VLS_ctx.help) { + __VLS_asFunctionalElement1(__VLS_intrinsics.small, __VLS_intrinsics.small)({ + id: (`${__VLS_ctx.resolvedId}-message`), + ...{ class: ({ 'ks-danger-text': __VLS_ctx.error }) }, + }); + /** @type {__VLS_StyleScopedClasses['ks-danger-text']} */ ; + (__VLS_ctx.error ?? __VLS_ctx.help); +} +// @ts-ignore +[resolvedId, error, error, error, help, help,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/components/KsDialog.vue.js b/frontend/src/shared/ui/components/KsDialog.vue.js new file mode 100644 index 00000000..dee86211 --- /dev/null +++ b/frontend/src/shared/ui/components/KsDialog.vue.js @@ -0,0 +1,56 @@ +import { useUiAdapter } from '../adapter/useUiAdapter'; +const __VLS_props = defineProps(); +const emit = defineEmits(); +const adapter = useUiAdapter(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = (__VLS_ctx.adapter.components.Dialog); +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onUpdate:visible': {} }, + ...(__VLS_ctx.$props), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onUpdate:visible': {} }, + ...(__VLS_ctx.$props), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.'update:visible'} */ + 'onUpdate:visible': (...[$event]) => { + return (__VLS_ctx.emit('update:visible', $event)); + // @ts-ignore + [adapter, $props, emit,]; + }, +}; +var __VLS_7; +const { default: __VLS_8 } = __VLS_3.slots; +var __VLS_9 = {}; +{ + const { footer: __VLS_11 } = __VLS_3.slots; + var __VLS_12 = {}; + // @ts-ignore + []; +} +// @ts-ignore +[]; +var __VLS_3; +var __VLS_4; +// @ts-ignore +var __VLS_10 = __VLS_9, __VLS_13 = __VLS_12; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/components/KsInlineMessage.vue.js b/frontend/src/shared/ui/components/KsInlineMessage.vue.js new file mode 100644 index 00000000..ad372a5f --- /dev/null +++ b/frontend/src/shared/ui/components/KsInlineMessage.vue.js @@ -0,0 +1,45 @@ +import { useUiAdapter } from '../adapter/useUiAdapter'; +const __VLS_props = withDefaults(defineProps(), { severity: 'info', dismissible: false }); +const emit = defineEmits(); +const adapter = useUiAdapter(); +const __VLS_defaults = { severity: 'info', dismissible: false }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = (__VLS_ctx.adapter.components.InlineMessage); +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onDismiss': {} }, + ...(__VLS_ctx.$props), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onDismiss': {} }, + ...(__VLS_ctx.$props), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.dismiss} */ + onDismiss: (...[$event]) => { + return (__VLS_ctx.emit('dismiss')); + // @ts-ignore + [adapter, $props, emit,]; + }, +}; +var __VLS_7; +var __VLS_3; +var __VLS_4; +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/components/KsMultiSelect.vue.js b/frontend/src/shared/ui/components/KsMultiSelect.vue.js new file mode 100644 index 00000000..42721e52 --- /dev/null +++ b/frontend/src/shared/ui/components/KsMultiSelect.vue.js @@ -0,0 +1,45 @@ +import { useUiAdapter } from '../adapter/useUiAdapter'; +const __VLS_props = withDefaults(defineProps(), { modelValue: () => [] }); +const emit = defineEmits(); +const adapter = useUiAdapter(); +const __VLS_defaults = { modelValue: () => [] }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = (__VLS_ctx.adapter.components.MultiSelect); +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onUpdate:modelValue': {} }, + ...(__VLS_ctx.$props), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onUpdate:modelValue': {} }, + ...(__VLS_ctx.$props), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.'update:modelValue'} */ + 'onUpdate:modelValue': (...[$event]) => { + return (__VLS_ctx.emit('update:modelValue', $event)); + // @ts-ignore + [adapter, $props, emit,]; + }, +}; +var __VLS_7; +var __VLS_3; +var __VLS_4; +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/components/KsNumberField.vue.js b/frontend/src/shared/ui/components/KsNumberField.vue.js new file mode 100644 index 00000000..f0f14977 --- /dev/null +++ b/frontend/src/shared/ui/components/KsNumberField.vue.js @@ -0,0 +1,92 @@ +import { computed, useId } from 'vue'; +import { useUiAdapter } from '../adapter/useUiAdapter'; +const props = defineProps(); +const emit = defineEmits(); +const adapter = useUiAdapter(); +const generatedId = useId(); +const resolvedId = computed(() => props.inputId ?? `ks-number-${generatedId}`); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-field" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-field']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.label, __VLS_intrinsics.label)({ + for: (__VLS_ctx.resolvedId), +}); +(__VLS_ctx.label); +if (__VLS_ctx.required) { + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({ + 'aria-hidden': "true", + }); +} +const __VLS_0 = (__VLS_ctx.adapter.components.NumberField); +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + inputId: (__VLS_ctx.resolvedId), + modelValue: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), + invalid: (Boolean(__VLS_ctx.error)), + min: (__VLS_ctx.min), + max: (__VLS_ctx.max), + minFractionDigits: (__VLS_ctx.minFractionDigits), + maxFractionDigits: (__VLS_ctx.maxFractionDigits), + 'aria-describedby': (__VLS_ctx.error || __VLS_ctx.help ? `${__VLS_ctx.resolvedId}-message` : undefined), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + inputId: (__VLS_ctx.resolvedId), + modelValue: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), + invalid: (Boolean(__VLS_ctx.error)), + min: (__VLS_ctx.min), + max: (__VLS_ctx.max), + minFractionDigits: (__VLS_ctx.minFractionDigits), + maxFractionDigits: (__VLS_ctx.maxFractionDigits), + 'aria-describedby': (__VLS_ctx.error || __VLS_ctx.help ? `${__VLS_ctx.resolvedId}-message` : undefined), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.'update:modelValue'} */ + 'onUpdate:modelValue': (...[$event]) => { + return (__VLS_ctx.emit('update:modelValue', $event)); + // @ts-ignore + [resolvedId, resolvedId, resolvedId, label, required, adapter, modelValue, disabled, error, error, min, max, minFractionDigits, maxFractionDigits, help, emit,]; + }, +}; +const __VLS_7 = { + /** @type {typeof __VLS_5.blur} */ + onBlur: (...[$event]) => { + return (__VLS_ctx.emit('blur', $event)); + // @ts-ignore + [emit,]; + }, +}; +var __VLS_3; +var __VLS_4; +if (__VLS_ctx.error || __VLS_ctx.help) { + __VLS_asFunctionalElement1(__VLS_intrinsics.small, __VLS_intrinsics.small)({ + id: (`${__VLS_ctx.resolvedId}-message`), + ...{ class: ({ 'ks-danger-text': __VLS_ctx.error }) }, + }); + /** @type {__VLS_StyleScopedClasses['ks-danger-text']} */ ; + (__VLS_ctx.error ?? __VLS_ctx.help); +} +// @ts-ignore +[resolvedId, error, error, error, help, help,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/components/KsPaginator.vue.js b/frontend/src/shared/ui/components/KsPaginator.vue.js new file mode 100644 index 00000000..f8624cf7 --- /dev/null +++ b/frontend/src/shared/ui/components/KsPaginator.vue.js @@ -0,0 +1,45 @@ +import { useUiAdapter } from '../adapter/useUiAdapter'; +const __VLS_props = withDefaults(defineProps(), { pageSizes: () => [20, 50, 100], disabled: false }); +const emit = defineEmits(); +const adapter = useUiAdapter(); +const __VLS_defaults = { pageSizes: () => [20, 50, 100], disabled: false }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = (__VLS_ctx.adapter.components.Paginator); +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onPageChange': {} }, + ...(__VLS_ctx.$props), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onPageChange': {} }, + ...(__VLS_ctx.$props), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.pageChange} */ + onPageChange: (...[$event]) => { + return (__VLS_ctx.emit('pageChange', $event)); + // @ts-ignore + [adapter, $props, emit,]; + }, +}; +var __VLS_7; +var __VLS_3; +var __VLS_4; +// @ts-ignore +[]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/components/KsSelect.vue.js b/frontend/src/shared/ui/components/KsSelect.vue.js new file mode 100644 index 00000000..d728a7df --- /dev/null +++ b/frontend/src/shared/ui/components/KsSelect.vue.js @@ -0,0 +1,85 @@ +import { computed, useId } from 'vue'; +import { useUiAdapter } from '../adapter/useUiAdapter'; +const props = defineProps(); +const emit = defineEmits(); +const adapter = useUiAdapter(); +const generatedId = useId(); +const resolvedId = computed(() => props.inputId ?? `ks-select-${generatedId}`); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-field" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-field']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.label, __VLS_intrinsics.label)({ + for: (__VLS_ctx.resolvedId), +}); +(__VLS_ctx.label); +if (__VLS_ctx.required) { + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({ + 'aria-hidden': "true", + }); +} +const __VLS_0 = (__VLS_ctx.adapter.components.Select); +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + inputId: (__VLS_ctx.resolvedId), + modelValue: (__VLS_ctx.modelValue), + options: (__VLS_ctx.options), + disabled: (__VLS_ctx.disabled), + invalid: (Boolean(__VLS_ctx.error)), + placeholder: (__VLS_ctx.placeholder), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + inputId: (__VLS_ctx.resolvedId), + modelValue: (__VLS_ctx.modelValue), + options: (__VLS_ctx.options), + disabled: (__VLS_ctx.disabled), + invalid: (Boolean(__VLS_ctx.error)), + placeholder: (__VLS_ctx.placeholder), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.'update:modelValue'} */ + 'onUpdate:modelValue': (...[$event]) => { + return (__VLS_ctx.emit('update:modelValue', $event)); + // @ts-ignore + [resolvedId, resolvedId, label, required, adapter, modelValue, options, disabled, error, placeholder, emit,]; + }, +}; +const __VLS_7 = { + /** @type {typeof __VLS_5.blur} */ + onBlur: (...[$event]) => { + return (__VLS_ctx.emit('blur', $event)); + // @ts-ignore + [emit,]; + }, +}; +var __VLS_3; +var __VLS_4; +if (__VLS_ctx.error || __VLS_ctx.help) { + __VLS_asFunctionalElement1(__VLS_intrinsics.small, __VLS_intrinsics.small)({ + ...{ class: ({ 'ks-danger-text': __VLS_ctx.error }) }, + }); + /** @type {__VLS_StyleScopedClasses['ks-danger-text']} */ ; + (__VLS_ctx.error ?? __VLS_ctx.help); +} +// @ts-ignore +[error, error, error, help, help,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/components/KsStatusTag.vue.js b/frontend/src/shared/ui/components/KsStatusTag.vue.js new file mode 100644 index 00000000..7299be03 --- /dev/null +++ b/frontend/src/shared/ui/components/KsStatusTag.vue.js @@ -0,0 +1,28 @@ +import { useUiAdapter } from '../adapter/useUiAdapter'; +const __VLS_props = defineProps(); +const adapter = useUiAdapter(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = (__VLS_ctx.adapter.components.StatusTag); +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...(__VLS_ctx.$props), +})); +const __VLS_2 = __VLS_1({ + ...(__VLS_ctx.$props), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +var __VLS_3; +// @ts-ignore +[adapter, $props,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/components/KsTabs.vue.js b/frontend/src/shared/ui/components/KsTabs.vue.js new file mode 100644 index 00000000..7a918b28 --- /dev/null +++ b/frontend/src/shared/ui/components/KsTabs.vue.js @@ -0,0 +1,60 @@ +import { useUiAdapter } from '../adapter/useUiAdapter'; +const __VLS_props = withDefaults(defineProps(), { ariaLabel: '탭' }); +const emit = defineEmits(); +const adapter = useUiAdapter(); +const __VLS_defaults = { ariaLabel: '탭' }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = (__VLS_ctx.adapter.components.Tabs); +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onUpdate:modelValue': {} }, + ...(__VLS_ctx.$props), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onUpdate:modelValue': {} }, + ...(__VLS_ctx.$props), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.'update:modelValue'} */ + 'onUpdate:modelValue': (...[$event]) => { + return (__VLS_ctx.emit('update:modelValue', $event)); + // @ts-ignore + [adapter, $props, emit,]; + }, +}; +var __VLS_7; +const { default: __VLS_8 } = __VLS_3.slots; +{ + const { default: __VLS_9 } = __VLS_3.slots; + const [slotProps] = __VLS_vSlot(__VLS_9); + var __VLS_10 = { + activeId: (slotProps.activeId), + }; + // @ts-ignore + []; +} +// @ts-ignore +[]; +var __VLS_3; +var __VLS_4; +// @ts-ignore +var __VLS_11 = __VLS_10; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/components/KsTextArea.vue.js b/frontend/src/shared/ui/components/KsTextArea.vue.js new file mode 100644 index 00000000..f2e07b6f --- /dev/null +++ b/frontend/src/shared/ui/components/KsTextArea.vue.js @@ -0,0 +1,85 @@ +import { computed, useId } from 'vue'; +import { useUiAdapter } from '../adapter/useUiAdapter'; +const props = defineProps(); +const emit = defineEmits(); +const adapter = useUiAdapter(); +const generatedId = useId(); +const resolvedId = computed(() => props.inputId ?? `ks-area-${generatedId}`); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-field" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-field']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.label, __VLS_intrinsics.label)({ + for: (__VLS_ctx.resolvedId), +}); +(__VLS_ctx.label); +if (__VLS_ctx.required) { + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({ + 'aria-hidden': "true", + }); +} +const __VLS_0 = (__VLS_ctx.adapter.components.TextArea); +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + inputId: (__VLS_ctx.resolvedId), + modelValue: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), + invalid: (Boolean(__VLS_ctx.error)), + rows: (__VLS_ctx.rows), + placeholder: (__VLS_ctx.placeholder), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + inputId: (__VLS_ctx.resolvedId), + modelValue: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), + invalid: (Boolean(__VLS_ctx.error)), + rows: (__VLS_ctx.rows), + placeholder: (__VLS_ctx.placeholder), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.'update:modelValue'} */ + 'onUpdate:modelValue': (...[$event]) => { + return (__VLS_ctx.emit('update:modelValue', $event)); + // @ts-ignore + [resolvedId, resolvedId, label, required, adapter, modelValue, disabled, error, rows, placeholder, emit,]; + }, +}; +const __VLS_7 = { + /** @type {typeof __VLS_5.blur} */ + onBlur: (...[$event]) => { + return (__VLS_ctx.emit('blur', $event)); + // @ts-ignore + [emit,]; + }, +}; +var __VLS_3; +var __VLS_4; +if (__VLS_ctx.error || __VLS_ctx.help) { + __VLS_asFunctionalElement1(__VLS_intrinsics.small, __VLS_intrinsics.small)({ + ...{ class: ({ 'ks-danger-text': __VLS_ctx.error }) }, + }); + /** @type {__VLS_StyleScopedClasses['ks-danger-text']} */ ; + (__VLS_ctx.error ?? __VLS_ctx.help); +} +// @ts-ignore +[error, error, error, help, help,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/components/KsTextField.vue.js b/frontend/src/shared/ui/components/KsTextField.vue.js new file mode 100644 index 00000000..12ebe081 --- /dev/null +++ b/frontend/src/shared/ui/components/KsTextField.vue.js @@ -0,0 +1,86 @@ +import { computed, useId } from 'vue'; +import { useUiAdapter } from '../adapter/useUiAdapter'; +const props = defineProps(); +const emit = defineEmits(); +const adapter = useUiAdapter(); +const generatedId = useId(); +const resolvedId = computed(() => props.inputId ?? `ks-field-${generatedId}`); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-field" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-field']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.label, __VLS_intrinsics.label)({ + for: (__VLS_ctx.resolvedId), +}); +(__VLS_ctx.label); +if (__VLS_ctx.required) { + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({ + 'aria-hidden': "true", + }); +} +const __VLS_0 = (__VLS_ctx.adapter.components.TextField); +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + inputId: (__VLS_ctx.resolvedId), + modelValue: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), + invalid: (Boolean(__VLS_ctx.error)), + placeholder: (__VLS_ctx.placeholder), + 'aria-describedby': (__VLS_ctx.error || __VLS_ctx.help ? `${__VLS_ctx.resolvedId}-message` : undefined), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onUpdate:modelValue': {} }, + ...{ 'onBlur': {} }, + inputId: (__VLS_ctx.resolvedId), + modelValue: (__VLS_ctx.modelValue), + disabled: (__VLS_ctx.disabled), + invalid: (Boolean(__VLS_ctx.error)), + placeholder: (__VLS_ctx.placeholder), + 'aria-describedby': (__VLS_ctx.error || __VLS_ctx.help ? `${__VLS_ctx.resolvedId}-message` : undefined), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.'update:modelValue'} */ + 'onUpdate:modelValue': (...[$event]) => { + return (__VLS_ctx.emit('update:modelValue', $event)); + // @ts-ignore + [resolvedId, resolvedId, resolvedId, label, required, adapter, modelValue, disabled, error, error, placeholder, help, emit,]; + }, +}; +const __VLS_7 = { + /** @type {typeof __VLS_5.blur} */ + onBlur: (...[$event]) => { + return (__VLS_ctx.emit('blur', $event)); + // @ts-ignore + [emit,]; + }, +}; +var __VLS_3; +var __VLS_4; +if (__VLS_ctx.error || __VLS_ctx.help) { + __VLS_asFunctionalElement1(__VLS_intrinsics.small, __VLS_intrinsics.small)({ + id: (`${__VLS_ctx.resolvedId}-message`), + ...{ class: ({ 'ks-danger-text': __VLS_ctx.error }) }, + }); + /** @type {__VLS_StyleScopedClasses['ks-danger-text']} */ ; + (__VLS_ctx.error ?? __VLS_ctx.help); +} +// @ts-ignore +[resolvedId, error, error, error, help, help,]; +const __VLS_export = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +export default {}; diff --git a/frontend/src/shared/ui/components/index.js b/frontend/src/shared/ui/components/index.js new file mode 100644 index 00000000..8739bb23 --- /dev/null +++ b/frontend/src/shared/ui/components/index.js @@ -0,0 +1,17 @@ +export { default as KsButton } from './KsButton.vue'; +export { default as KsTextField } from './KsTextField.vue'; +export { default as KsTextArea } from './KsTextArea.vue'; +export { default as KsSelect } from './KsSelect.vue'; +export { default as KsMultiSelect } from './KsMultiSelect.vue'; +export { default as KsCheckbox } from './KsCheckbox.vue'; +export { default as KsDateField } from './KsDateField.vue'; +export { default as KsNumberField } from './KsNumberField.vue'; +export { default as KsDialog } from './KsDialog.vue'; +export { default as KsStatusTag } from './KsStatusTag.vue'; +export { default as KsInlineMessage } from './KsInlineMessage.vue'; +export { default as KsPaginator } from './KsPaginator.vue'; +export { default as KsTabs } from './KsTabs.vue'; +export { default as KsDataGrid } from './KsDataGrid.vue'; +export { default as FieldShell } from './FieldShell.vue'; +export { default as KsDataContextHeader } from './KsDataContextHeader.vue'; +export { default as KsCommandBar } from './KsCommandBar.vue'; diff --git a/frontend/src/shared/ui/contracts/screenContract.js b/frontend/src/shared/ui/contracts/screenContract.js new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/frontend/src/shared/ui/contracts/screenContract.js @@ -0,0 +1 @@ +export {}; diff --git a/frontend/src/shared/ui/feedback/StandardStatePanel.vue.js b/frontend/src/shared/ui/feedback/StandardStatePanel.vue.js new file mode 100644 index 00000000..46c69d13 --- /dev/null +++ b/frontend/src/shared/ui/feedback/StandardStatePanel.vue.js @@ -0,0 +1,66 @@ +const __VLS_props = withDefaults(defineProps(), { state: 'READY', retryable: false }); +const __VLS_emit = defineEmits(); +const __VLS_defaults = { state: 'READY', retryable: false }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +/** @type {__VLS_StyleScopedClasses['ks-state']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-state']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-state']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-state']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-state']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-state']} */ ; +if (__VLS_ctx.state !== 'READY') { + __VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-state" }, + 'data-state': (__VLS_ctx.state), + role: "status", + 'aria-busy': (__VLS_ctx.state === 'LOADING' || __VLS_ctx.state === 'PROCESSING'), + }); + /** @type {__VLS_StyleScopedClasses['ks-state']} */ ; + __VLS_asFunctionalElement1(__VLS_intrinsics.strong, __VLS_intrinsics.strong)({}); + (__VLS_ctx.title ?? __VLS_ctx.state); + if (__VLS_ctx.message) { + __VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({}); + (__VLS_ctx.message); + } + if (__VLS_ctx.traceId) { + __VLS_asFunctionalElement1(__VLS_intrinsics.small, __VLS_intrinsics.small)({}); + (__VLS_ctx.traceId); + } + if (__VLS_ctx.retryable) { + __VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({ + ...{ onClick: (...[$event]) => { + if (!(__VLS_ctx.state !== 'READY')) + throw 0; + if (!(__VLS_ctx.retryable)) + throw 0; + return (__VLS_ctx.$emit('retry')); + // @ts-ignore + [state, state, state, state, state, title, message, message, traceId, traceId, retryable, $emit,]; + } }, + type: "button", + }); + } + var __VLS_0 = {}; +} +else { + var __VLS_2 = {}; +} +// @ts-ignore +var __VLS_1 = __VLS_0, __VLS_3 = __VLS_2; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/index.js b/frontend/src/shared/ui/index.js new file mode 100644 index 00000000..5e9e6f3b --- /dev/null +++ b/frontend/src/shared/ui/index.js @@ -0,0 +1,7 @@ +export * from './components'; +export * from './layouts'; +export * from './screen-types'; +export { default as QueryStateBoundary } from './QueryStateBoundary.vue'; +export { default as EvidenceVersionSet } from './EvidenceVersionSet.vue'; +export { default as DataGridShell } from './DataGridShell.vue'; +export { default as VersionConflictDialog } from './VersionConflictDialog.vue'; diff --git a/frontend/src/shared/ui/layouts/AppShellLayout.vue.js b/frontend/src/shared/ui/layouts/AppShellLayout.vue.js new file mode 100644 index 00000000..0a90956e --- /dev/null +++ b/frontend/src/shared/ui/layouts/AppShellLayout.vue.js @@ -0,0 +1,67 @@ +const __VLS_props = defineProps(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +/** @type {__VLS_StyleScopedClasses['ks-shell__header']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-shell__header']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-skip']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-shell']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-shell__header']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-shell__nav']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-shell" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-shell']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.a, __VLS_intrinsics.a)({ + ...{ class: "ks-skip" }, + href: "#ks-main", +}); +/** @type {__VLS_StyleScopedClasses['ks-skip']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.header, __VLS_intrinsics.header)({ + ...{ class: "ks-shell__header" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-shell__header']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.strong, __VLS_intrinsics.strong)({}); +(__VLS_ctx.productName ?? 'K-ArtSell Aegis'); +__VLS_asFunctionalElement1(__VLS_intrinsics.small, __VLS_intrinsics.small)({}); +(__VLS_ctx.environment ?? 'IMPLEMENTATION_TEMPLATE'); +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-shell__boundary" }, + role: "status", +}); +/** @type {__VLS_StyleScopedClasses['ks-shell__boundary']} */ ; +(__VLS_ctx.automationStatus ?? '투자자문형 · 자동주문/KIS 제출 OFF · 자동 모델승격 OFF'); +var __VLS_0 = {}; +__VLS_asFunctionalElement1(__VLS_intrinsics.aside, __VLS_intrinsics.aside)({ + ...{ class: "ks-shell__nav" }, + 'aria-label': "주요 메뉴", +}); +/** @type {__VLS_StyleScopedClasses['ks-shell__nav']} */ ; +var __VLS_2 = {}; +__VLS_asFunctionalElement1(__VLS_intrinsics.main, __VLS_intrinsics.main)({ + id: "ks-main", + ...{ class: "ks-shell__main" }, + tabindex: "-1", +}); +/** @type {__VLS_StyleScopedClasses['ks-shell__main']} */ ; +var __VLS_4 = {}; +__VLS_asFunctionalElement1(__VLS_intrinsics.footer, __VLS_intrinsics.footer)({ + ...{ class: "ks-shell__footer" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-shell__footer']} */ ; +var __VLS_6 = {}; +// @ts-ignore +var __VLS_1 = __VLS_0, __VLS_3 = __VLS_2, __VLS_5 = __VLS_4, __VLS_7 = __VLS_6; +// @ts-ignore +[productName, environment, automationStatus,]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/layouts/CrudWorkspaceLayout.vue.js b/frontend/src/shared/ui/layouts/CrudWorkspaceLayout.vue.js new file mode 100644 index 00000000..17081045 --- /dev/null +++ b/frontend/src/shared/ui/layouts/CrudWorkspaceLayout.vue.js @@ -0,0 +1,50 @@ +const __VLS_props = withDefaults(defineProps(), { asideWidth: '24rem', dense: false }); // @ts-ignore +const __VLS_defaults = { asideWidth: '24rem', dense: false }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +/** @type {__VLS_StyleScopedClasses['ks-crud-workspace__body']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-crud-workspace__body']} */ ; +/** @type {__VLS_StyleScopedClasses['has-aside']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + ...{ class: "ks-crud-workspace" }, + ...{ class: ({ dense: __VLS_ctx.dense }) }, + ...{ style: ({ '--ks-crud-aside': __VLS_ctx.asideWidth }) }, +}); +/** @type {__VLS_StyleScopedClasses['ks-crud-workspace']} */ ; +/** @type {__VLS_StyleScopedClasses['dense']} */ ; +if (__VLS_ctx.$slots.toolbar) { + __VLS_asFunctionalElement1(__VLS_intrinsics.header, __VLS_intrinsics.header)({}); + var __VLS_0 = {}; +} +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-crud-workspace__body" }, + ...{ class: ({ 'has-aside': __VLS_ctx.$slots.aside }) }, +}); +/** @type {__VLS_StyleScopedClasses['ks-crud-workspace__body']} */ ; +/** @type {__VLS_StyleScopedClasses['has-aside']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.main, __VLS_intrinsics.main)({}); +var __VLS_2 = {}; +if (__VLS_ctx.$slots.aside) { + __VLS_asFunctionalElement1(__VLS_intrinsics.aside, __VLS_intrinsics.aside)({}); + var __VLS_4 = {}; +} +if (__VLS_ctx.$slots.actions) { + __VLS_asFunctionalElement1(__VLS_intrinsics.footer, __VLS_intrinsics.footer)({}); + var __VLS_6 = {}; +} +// @ts-ignore +var __VLS_1 = __VLS_0, __VLS_3 = __VLS_2, __VLS_5 = __VLS_4, __VLS_7 = __VLS_6; +// @ts-ignore +[dense, asideWidth, $slots, $slots, $slots, $slots,]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeProps: {}, + props: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/layouts/DashboardLayout.vue.js b/frontend/src/shared/ui/layouts/DashboardLayout.vue.js new file mode 100644 index 00000000..69adb646 --- /dev/null +++ b/frontend/src/shared/ui/layouts/DashboardLayout.vue.js @@ -0,0 +1,38 @@ +const __VLS_ctx = {}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +/** @type {__VLS_StyleScopedClasses['ks-dashboard__alerts']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-dashboard']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-dashboard" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-dashboard']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + ...{ class: "ks-dashboard__kpis" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-dashboard__kpis']} */ ; +var __VLS_0 = {}; +__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + ...{ class: "ks-dashboard__primary ks-card" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-dashboard__primary']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-card']} */ ; +var __VLS_2 = {}; +__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + ...{ class: "ks-dashboard__secondary ks-card" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-dashboard__secondary']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-card']} */ ; +var __VLS_4 = {}; +__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + ...{ class: "ks-dashboard__alerts ks-card" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-dashboard__alerts']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-card']} */ ; +var __VLS_6 = {}; +// @ts-ignore +var __VLS_1 = __VLS_0, __VLS_3 = __VLS_2, __VLS_5 = __VLS_4, __VLS_7 = __VLS_6; +const __VLS_base = (await import('vue')).defineComponent({}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/layouts/FormPageLayout.vue.js b/frontend/src/shared/ui/layouts/FormPageLayout.vue.js new file mode 100644 index 00000000..fff9feb8 --- /dev/null +++ b/frontend/src/shared/ui/layouts/FormPageLayout.vue.js @@ -0,0 +1,43 @@ +const __VLS_emit = defineEmits(); // @ts-ignore +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +/** @type {__VLS_StyleScopedClasses['ks-form-layout']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-form-layout" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-form-layout']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.form, __VLS_intrinsics.form)({ + ...{ onSubmit: (...[$event]) => { + return (__VLS_ctx.$emit('submit')); + // @ts-ignore + [$emit,]; + } }, + ...{ class: "ks-form-layout__form ks-card" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-form-layout__form']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-card']} */ ; +var __VLS_0 = {}; +if (__VLS_ctx.$slots.preview) { + __VLS_asFunctionalElement1(__VLS_intrinsics.aside, __VLS_intrinsics.aside)({ + ...{ class: "ks-form-layout__preview ks-card" }, + }); + /** @type {__VLS_StyleScopedClasses['ks-form-layout__preview']} */ ; + /** @type {__VLS_StyleScopedClasses['ks-card']} */ ; + var __VLS_2 = {}; +} +// @ts-ignore +var __VLS_1 = __VLS_0, __VLS_3 = __VLS_2; +// @ts-ignore +[$slots,]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/layouts/OperationsConsoleLayout.vue.js b/frontend/src/shared/ui/layouts/OperationsConsoleLayout.vue.js new file mode 100644 index 00000000..e8b17e12 --- /dev/null +++ b/frontend/src/shared/ui/layouts/OperationsConsoleLayout.vue.js @@ -0,0 +1,44 @@ +const __VLS_props = defineProps(); // @ts-ignore +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +/** @type {__VLS_StyleScopedClasses['ks-ops-console__grid']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + ...{ class: "ks-ops-console" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-ops-console']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.header, __VLS_intrinsics.header)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.h1, __VLS_intrinsics.h1)({}); +(__VLS_ctx.title); +__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({}); +(__VLS_ctx.runStatus ?? '-'); +(__VLS_ctx.watermark ?? '-'); +(__VLS_ctx.owner ?? '-'); +var __VLS_0 = {}; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-ops-console__grid" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-ops-console__grid']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.aside, __VLS_intrinsics.aside)({}); +var __VLS_2 = {}; +__VLS_asFunctionalElement1(__VLS_intrinsics.main, __VLS_intrinsics.main)({}); +var __VLS_4 = {}; +if (__VLS_ctx.$slots.footer) { + __VLS_asFunctionalElement1(__VLS_intrinsics.footer, __VLS_intrinsics.footer)({}); + var __VLS_6 = {}; +} +// @ts-ignore +var __VLS_1 = __VLS_0, __VLS_3 = __VLS_2, __VLS_5 = __VLS_4, __VLS_7 = __VLS_6; +// @ts-ignore +[title, runStatus, watermark, owner, $slots,]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/layouts/PageLayout.vue.js b/frontend/src/shared/ui/layouts/PageLayout.vue.js new file mode 100644 index 00000000..838d25a9 --- /dev/null +++ b/frontend/src/shared/ui/layouts/PageLayout.vue.js @@ -0,0 +1,98 @@ +const __VLS_props = defineProps(); +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +/** @type {__VLS_StyleScopedClasses['ks-page__workspace']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-page__workspace']} */ ; +/** @type {__VLS_StyleScopedClasses['has-aside']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + ...{ class: "ks-page" }, + ...{ style: ({ '--ks-aside-width': __VLS_ctx.asideWidth ?? '22rem' }) }, +}); +/** @type {__VLS_StyleScopedClasses['ks-page']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.header, __VLS_intrinsics.header)({ + ...{ class: "ks-page__header" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-page__header']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({}); +__VLS_asFunctionalElement1(__VLS_intrinsics.h1, __VLS_intrinsics.h1)({}); +(__VLS_ctx.title); +if (__VLS_ctx.subtitle) { + __VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({}); + (__VLS_ctx.subtitle); +} +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-page__meta" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-page__meta']} */ ; +if (__VLS_ctx.status) { + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({}); + (__VLS_ctx.status); +} +if (__VLS_ctx.asOf) { + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({}); + (__VLS_ctx.asOf); +} +if (__VLS_ctx.version) { + __VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({}); + (__VLS_ctx.version); +} +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-page__actions" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-page__actions']} */ ; +var __VLS_0 = {}; +if (__VLS_ctx.$slots.summary) { + __VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-page__summary" }, + }); + /** @type {__VLS_StyleScopedClasses['ks-page__summary']} */ ; + var __VLS_2 = {}; +} +if (__VLS_ctx.$slots.filters) { + __VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-page__filters ks-card" }, + }); + /** @type {__VLS_StyleScopedClasses['ks-page__filters']} */ ; + /** @type {__VLS_StyleScopedClasses['ks-card']} */ ; + var __VLS_4 = {}; +} +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-page__workspace" }, + ...{ class: ({ 'has-aside': __VLS_ctx.$slots.aside }) }, +}); +/** @type {__VLS_StyleScopedClasses['ks-page__workspace']} */ ; +/** @type {__VLS_StyleScopedClasses['has-aside']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-page__content" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-page__content']} */ ; +var __VLS_6 = {}; +if (__VLS_ctx.$slots.aside) { + __VLS_asFunctionalElement1(__VLS_intrinsics.aside, __VLS_intrinsics.aside)({ + ...{ class: "ks-page__aside" }, + }); + /** @type {__VLS_StyleScopedClasses['ks-page__aside']} */ ; + var __VLS_8 = {}; +} +if (__VLS_ctx.$slots.footer) { + __VLS_asFunctionalElement1(__VLS_intrinsics.footer, __VLS_intrinsics.footer)({ + ...{ class: "ks-page__footer" }, + }); + /** @type {__VLS_StyleScopedClasses['ks-page__footer']} */ ; + var __VLS_10 = {}; +} +// @ts-ignore +var __VLS_1 = __VLS_0, __VLS_3 = __VLS_2, __VLS_5 = __VLS_4, __VLS_7 = __VLS_6, __VLS_9 = __VLS_8, __VLS_11 = __VLS_10; +// @ts-ignore +[asideWidth, title, subtitle, subtitle, status, status, asOf, asOf, version, version, $slots, $slots, $slots, $slots, $slots,]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/layouts/ReviewWorkbenchLayout.vue.js b/frontend/src/shared/ui/layouts/ReviewWorkbenchLayout.vue.js new file mode 100644 index 00000000..ac1a84ba --- /dev/null +++ b/frontend/src/shared/ui/layouts/ReviewWorkbenchLayout.vue.js @@ -0,0 +1,36 @@ +const __VLS_ctx = {}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +/** @type {__VLS_StyleScopedClasses['ks-review-workbench']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-review-workbench']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-review-workbench" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-review-workbench']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + ...{ class: "ks-review-workbench__queue ks-card" }, + 'aria-label': "검토 대기열", +}); +/** @type {__VLS_StyleScopedClasses['ks-review-workbench__queue']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-card']} */ ; +var __VLS_0 = {}; +__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + ...{ class: "ks-review-workbench__detail ks-card" }, + 'aria-label': "결정 상세", +}); +/** @type {__VLS_StyleScopedClasses['ks-review-workbench__detail']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-card']} */ ; +var __VLS_2 = {}; +__VLS_asFunctionalElement1(__VLS_intrinsics.aside, __VLS_intrinsics.aside)({ + ...{ class: "ks-review-workbench__decision ks-card" }, + 'aria-label': "검토 결정", +}); +/** @type {__VLS_StyleScopedClasses['ks-review-workbench__decision']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-card']} */ ; +var __VLS_4 = {}; +// @ts-ignore +var __VLS_1 = __VLS_0, __VLS_3 = __VLS_2, __VLS_5 = __VLS_4; +const __VLS_base = (await import('vue')).defineComponent({}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/layouts/index.js b/frontend/src/shared/ui/layouts/index.js new file mode 100644 index 00000000..c369ca08 --- /dev/null +++ b/frontend/src/shared/ui/layouts/index.js @@ -0,0 +1,7 @@ +export { default as AppShellLayout } from './AppShellLayout.vue'; +export { default as PageLayout } from './PageLayout.vue'; +export { default as DashboardLayout } from './DashboardLayout.vue'; +export { default as FormPageLayout } from './FormPageLayout.vue'; +export { default as ReviewWorkbenchLayout } from './ReviewWorkbenchLayout.vue'; +export { default as CrudWorkspaceLayout } from './CrudWorkspaceLayout.vue'; +export { default as OperationsConsoleLayout } from './OperationsConsoleLayout.vue'; diff --git a/frontend/src/shared/ui/provider/UiProvider.js b/frontend/src/shared/ui/provider/UiProvider.js new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/frontend/src/shared/ui/provider/UiProvider.js @@ -0,0 +1 @@ +export {}; diff --git a/frontend/src/shared/ui/provider/index.js b/frontend/src/shared/ui/provider/index.js new file mode 100644 index 00000000..c226b86e --- /dev/null +++ b/frontend/src/shared/ui/provider/index.js @@ -0,0 +1 @@ +export { resolveUiProvider } from './resolveUiProvider'; diff --git a/frontend/src/shared/ui/provider/resolveUiProvider.js b/frontend/src/shared/ui/provider/resolveUiProvider.js new file mode 100644 index 00000000..e6f0ede9 --- /dev/null +++ b/frontend/src/shared/ui/provider/resolveUiProvider.js @@ -0,0 +1,10 @@ +import { nativeUiProvider } from '../adapter/native/installNativeAdapter'; +import { primeVueUiProvider } from '../adapter/primevue/installPrimeVueAdapter'; +export function resolveUiProvider(name) { + const normalized = (name ?? 'primevue').trim().toLowerCase(); + if (normalized === 'primevue') + return primeVueUiProvider; + if (normalized === 'native') + return nativeUiProvider; + throw new Error(`Unsupported VITE_UI_ADAPTER '${name}'. Allowed values: primevue, native.`); +} diff --git a/frontend/src/shared/ui/screen-types/BatchOperationsPage.vue.js b/frontend/src/shared/ui/screen-types/BatchOperationsPage.vue.js new file mode 100644 index 00000000..6dcfa313 --- /dev/null +++ b/frontend/src/shared/ui/screen-types/BatchOperationsPage.vue.js @@ -0,0 +1,72 @@ +import PageLayout from '../layouts/PageLayout.vue'; +const __VLS_props = defineProps(); // @ts-ignore +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = PageLayout || PageLayout; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...(__VLS_ctx.$props), +})); +const __VLS_2 = __VLS_1({ + ...(__VLS_ctx.$props), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +{ + const { actions: __VLS_7 } = __VLS_3.slots; + var __VLS_8 = {}; + // @ts-ignore + [$props,]; +} +{ + const { summary: __VLS_10 } = __VLS_3.slots; + var __VLS_11 = {}; + // @ts-ignore + []; +} +__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + ...{ class: "ks-stack" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-stack']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card ks-section" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-card']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-section']} */ ; +var __VLS_13 = {}; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card ks-section" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-card']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-section']} */ ; +var __VLS_15 = {}; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card ks-section" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-card']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-section']} */ ; +var __VLS_17 = {}; +{ + const { aside: __VLS_19 } = __VLS_3.slots; + var __VLS_20 = {}; + // @ts-ignore + []; +} +// @ts-ignore +[]; +var __VLS_3; +// @ts-ignore +var __VLS_9 = __VLS_8, __VLS_12 = __VLS_11, __VLS_14 = __VLS_13, __VLS_16 = __VLS_15, __VLS_18 = __VLS_17, __VLS_21 = __VLS_20; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/screen-types/CrudDetailPage.vue.js b/frontend/src/shared/ui/screen-types/CrudDetailPage.vue.js new file mode 100644 index 00000000..d48b1c12 --- /dev/null +++ b/frontend/src/shared/ui/screen-types/CrudDetailPage.vue.js @@ -0,0 +1,57 @@ +import PageLayout from '../layouts/PageLayout.vue'; +const __VLS_props = defineProps(); // @ts-ignore +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = PageLayout || PageLayout; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...(__VLS_ctx.$props), +})); +const __VLS_2 = __VLS_1({ + ...(__VLS_ctx.$props), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +{ + const { actions: __VLS_7 } = __VLS_3.slots; + var __VLS_8 = {}; + // @ts-ignore + [$props,]; +} +{ + const { summary: __VLS_10 } = __VLS_3.slots; + var __VLS_11 = {}; + // @ts-ignore + []; +} +var __VLS_13 = {}; +{ + const { aside: __VLS_15 } = __VLS_3.slots; + var __VLS_16 = {}; + // @ts-ignore + []; +} +{ + const { footer: __VLS_18 } = __VLS_3.slots; + var __VLS_19 = {}; + // @ts-ignore + []; +} +// @ts-ignore +[]; +var __VLS_3; +// @ts-ignore +var __VLS_9 = __VLS_8, __VLS_12 = __VLS_11, __VLS_14 = __VLS_13, __VLS_17 = __VLS_16, __VLS_20 = __VLS_19; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/screen-types/CrudFormPage.vue.js b/frontend/src/shared/ui/screen-types/CrudFormPage.vue.js new file mode 100644 index 00000000..40eb65e7 --- /dev/null +++ b/frontend/src/shared/ui/screen-types/CrudFormPage.vue.js @@ -0,0 +1,77 @@ +import PageLayout from '../layouts/PageLayout.vue'; +import FormPageLayout from '../layouts/FormPageLayout.vue'; +const __VLS_props = defineProps(); +const __VLS_emit = defineEmits(); // @ts-ignore +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = PageLayout || PageLayout; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...(__VLS_ctx.$props), +})); +const __VLS_2 = __VLS_1({ + ...(__VLS_ctx.$props), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +{ + const { actions: __VLS_7 } = __VLS_3.slots; + var __VLS_8 = {}; + // @ts-ignore + [$props,]; +} +const __VLS_10 = FormPageLayout || FormPageLayout; +// @ts-ignore +const __VLS_11 = __VLS_asFunctionalComponent1(__VLS_10, new __VLS_10({ + ...{ 'onSubmit': {} }, +})); +const __VLS_12 = __VLS_11({ + ...{ 'onSubmit': {} }, +}, ...__VLS_functionalComponentArgsRest(__VLS_11)); +let __VLS_15; +const __VLS_16 = { + /** @type {typeof __VLS_15.submit} */ + onSubmit: (...[$event]) => { + return (__VLS_ctx.$emit('submit')); + // @ts-ignore + [$emit,]; + }, +}; +const { default: __VLS_17 } = __VLS_13.slots; +var __VLS_18 = {}; +{ + const { preview: __VLS_20 } = __VLS_13.slots; + var __VLS_21 = {}; + // @ts-ignore + []; +} +// @ts-ignore +[]; +var __VLS_13; +var __VLS_14; +{ + const { footer: __VLS_23 } = __VLS_3.slots; + var __VLS_24 = {}; + // @ts-ignore + []; +} +// @ts-ignore +[]; +var __VLS_3; +// @ts-ignore +var __VLS_9 = __VLS_8, __VLS_19 = __VLS_18, __VLS_22 = __VLS_21, __VLS_25 = __VLS_24; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/screen-types/CrudListPage.vue.js b/frontend/src/shared/ui/screen-types/CrudListPage.vue.js new file mode 100644 index 00000000..3d526cdd --- /dev/null +++ b/frontend/src/shared/ui/screen-types/CrudListPage.vue.js @@ -0,0 +1,63 @@ +import PageLayout from '../layouts/PageLayout.vue'; +const __VLS_props = defineProps(); // @ts-ignore +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = PageLayout || PageLayout; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...(__VLS_ctx.$props), +})); +const __VLS_2 = __VLS_1({ + ...(__VLS_ctx.$props), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +{ + const { actions: __VLS_7 } = __VLS_3.slots; + var __VLS_8 = {}; + // @ts-ignore + [$props,]; +} +{ + const { summary: __VLS_10 } = __VLS_3.slots; + var __VLS_11 = {}; + // @ts-ignore + []; +} +{ + const { filters: __VLS_13 } = __VLS_3.slots; + var __VLS_14 = {}; + // @ts-ignore + []; +} +var __VLS_16 = {}; +{ + const { aside: __VLS_18 } = __VLS_3.slots; + var __VLS_19 = {}; + // @ts-ignore + []; +} +{ + const { footer: __VLS_21 } = __VLS_3.slots; + var __VLS_22 = {}; + // @ts-ignore + []; +} +// @ts-ignore +[]; +var __VLS_3; +// @ts-ignore +var __VLS_9 = __VLS_8, __VLS_12 = __VLS_11, __VLS_15 = __VLS_14, __VLS_17 = __VLS_16, __VLS_20 = __VLS_19, __VLS_23 = __VLS_22; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/screen-types/CrudReviewPage.vue.js b/frontend/src/shared/ui/screen-types/CrudReviewPage.vue.js new file mode 100644 index 00000000..6bb3c14b --- /dev/null +++ b/frontend/src/shared/ui/screen-types/CrudReviewPage.vue.js @@ -0,0 +1,65 @@ +import PageLayout from '../layouts/PageLayout.vue'; +import ReviewWorkbenchLayout from '../layouts/ReviewWorkbenchLayout.vue'; +const __VLS_props = defineProps(); // @ts-ignore +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = PageLayout || PageLayout; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...(__VLS_ctx.$props), +})); +const __VLS_2 = __VLS_1({ + ...(__VLS_ctx.$props), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +{ + const { actions: __VLS_7 } = __VLS_3.slots; + var __VLS_8 = {}; + // @ts-ignore + [$props,]; +} +const __VLS_10 = ReviewWorkbenchLayout || ReviewWorkbenchLayout; +// @ts-ignore +const __VLS_11 = __VLS_asFunctionalComponent1(__VLS_10, new __VLS_10({})); +const __VLS_12 = __VLS_11({}, ...__VLS_functionalComponentArgsRest(__VLS_11)); +const { default: __VLS_15 } = __VLS_13.slots; +{ + const { queue: __VLS_16 } = __VLS_13.slots; + var __VLS_17 = {}; + // @ts-ignore + []; +} +{ + const { detail: __VLS_19 } = __VLS_13.slots; + var __VLS_20 = {}; + // @ts-ignore + []; +} +{ + const { decision: __VLS_22 } = __VLS_13.slots; + var __VLS_23 = {}; + // @ts-ignore + []; +} +// @ts-ignore +[]; +var __VLS_13; +// @ts-ignore +[]; +var __VLS_3; +// @ts-ignore +var __VLS_9 = __VLS_8, __VLS_18 = __VLS_17, __VLS_21 = __VLS_20, __VLS_24 = __VLS_23; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/screen-types/catalogue.js b/frontend/src/shared/ui/screen-types/catalogue.js new file mode 100644 index 00000000..29c2363a --- /dev/null +++ b/frontend/src/shared/ui/screen-types/catalogue.js @@ -0,0 +1,18 @@ +export const screenTemplateCatalogue = Object.freeze([ + { id: 'T01', name: '검색·목록형 CRUD', component: 'SearchListCrudPage', intendedUse: '상품·고객·권한·데이터 Run·추천 검토함', mandatoryStates: ['LOADING', 'EMPTY', 'WARN', 'ERROR', 'UNAUTHORIZED', 'PARTIAL'], mandatoryEvidence: ['filter-url', 'permission', 'export-auth'], antiPatterns: ['client-side-all-data', 'unbounded-export'] }, + { id: 'T02', name: '상세 조회형', component: 'DetailReadPage', intendedUse: '투자제안·상품·포트폴리오·백테스트 결과', mandatoryStates: ['LOADING', 'WARN', 'EXPIRED', 'READONLY', 'UNAUTHORIZED'], mandatoryEvidence: ['as-of', 'version-set', 'audit'], antiPatterns: ['mutable-evidence', 'hidden-version'] }, + { id: 'T03', name: '등록·편집 Form', component: 'EditFormPage', intendedUse: '고객·IPS·비용표·권한·설정', mandatoryStates: ['DIRTY', 'CONFLICT', 'PROCESSING', 'ERROR'], mandatoryEvidence: ['zod', 'if-match', 'idempotency-key'], antiPatterns: ['silent-overwrite', 'pinia-form-cache'] }, + { id: 'T04', name: 'Master-Detail', component: 'MasterDetailCrudPage', intendedUse: '고객-IPS·추천-항목·Watch-Stage·대사 Run-Break', mandatoryStates: ['LOADING', 'EMPTY', 'DIRTY', 'PARTIAL', 'CONFLICT'], mandatoryEvidence: ['route-selection', 'unsaved-guard', 'version'], antiPatterns: ['selection-only-local', 'detail-n-plus-one'] }, + { id: 'T05', name: '검토·승인 Workbench', component: 'ApprovalWorkbenchPage', intendedUse: '추천·모델·정정·대사 maker-checker', mandatoryStates: ['WARN', 'EXPIRED', 'CONFLICT', 'PROCESSING', 'READONLY'], mandatoryEvidence: ['maker-checker', 'reason', 'warning-ack'], antiPatterns: ['self-approval', 'approval-without-evidence'] }, + { id: 'T06', name: '단계 Wizard', component: 'StepWizardPage', intendedUse: '고객 온보딩·IPS·리밸런싱·Backfill', mandatoryStates: ['DIRTY', 'ERROR', 'PROCESSING', 'READONLY'], mandatoryEvidence: ['resume', 'branch', 'impact-revalidation'], antiPatterns: ['single-huge-form', 'skip-validation'] }, + { id: 'T07', name: 'Dashboard·Scorecard', component: 'ScorecardDashboardPage', intendedUse: '고객 대시보드·일평가·운영 SLO', mandatoryStates: ['LOADING', 'EMPTY', 'WARN', 'PARTIAL'], mandatoryEvidence: ['metric-definition', 'sample-size', 'table-alternative'], antiPatterns: ['chart-only', 'metric-definition-hidden'] }, + { id: 'T08', name: 'Batch·데이터 운영', component: 'BatchOperationsPageV2', intendedUse: '수집·Feature·추천·평가·Backfill', mandatoryStates: ['PROCESSING', 'WARN', 'ERROR', 'PARTIAL', 'READONLY'], mandatoryEvidence: ['job-run', 'watermark', 'replay-scope'], antiPatterns: ['blind-retry', 'overwrite-reprocess'] }, + { id: 'T09', name: '대사·예외 처리', component: 'ReconciliationExceptionPage', intendedUse: 'KIS/원장 대사·데이터 격리·DQ 예외', mandatoryStates: ['WARN', 'CONFLICT', 'PROCESSING', 'READONLY'], mandatoryEvidence: ['before-after', 'correction', 'audit'], antiPatterns: ['direct-db-fix', 'delete-break'] }, + { id: 'T10', name: '버전 비교·거버넌스', component: 'VersionGovernancePage', intendedUse: '모델·정책·설정·데이터 공급원 승격', mandatoryStates: ['WARN', 'EXPIRED', 'READONLY', 'CONFLICT'], mandatoryEvidence: ['same-dataset-cost', 'gate-pack', 'rollback'], antiPatterns: ['auto-promotion', 'different-cohort-comparison'] } +]); +export function getScreenTemplate(id) { + const template = screenTemplateCatalogue.find(x => x.id === id); + if (!template) + throw new Error(`Unknown screen template: ${id}`); + return template; +} diff --git a/frontend/src/shared/ui/screen-types/index.js b/frontend/src/shared/ui/screen-types/index.js new file mode 100644 index 00000000..097acd6e --- /dev/null +++ b/frontend/src/shared/ui/screen-types/index.js @@ -0,0 +1,7 @@ +export { default as CrudListPage } from './CrudListPage.vue'; +export { default as CrudDetailPage } from './CrudDetailPage.vue'; +export { default as CrudFormPage } from './CrudFormPage.vue'; +export { default as CrudReviewPage } from './CrudReviewPage.vue'; +export { default as BatchOperationsPage } from './BatchOperationsPage.vue'; +export * from './v2'; +export * from './catalogue'; diff --git a/frontend/src/shared/ui/screen-types/tests/catalogue.spec.js b/frontend/src/shared/ui/screen-types/tests/catalogue.spec.js new file mode 100644 index 00000000..ca597b97 --- /dev/null +++ b/frontend/src/shared/ui/screen-types/tests/catalogue.spec.js @@ -0,0 +1,19 @@ +import { describe, expect, it } from 'vitest'; +import { getScreenTemplate, screenTemplateCatalogue } from '../catalogue'; +describe('screen template catalogue', () => { + it('defines all ten screen contracts without duplicate IDs or components', () => { + expect(screenTemplateCatalogue).toHaveLength(10); + expect(new Set(screenTemplateCatalogue.map(x => x.id)).size).toBe(10); + expect(new Set(screenTemplateCatalogue.map(x => x.component)).size).toBe(10); + }); + it('requires evidence and anti-pattern declarations for every template', () => { + for (const template of screenTemplateCatalogue) { + expect(template.mandatoryEvidence.length).toBeGreaterThan(0); + expect(template.mandatoryStates.length).toBeGreaterThan(0); + expect(template.antiPatterns.length).toBeGreaterThan(0); + } + }); + it('resolves a template by stable ID', () => { + expect(getScreenTemplate('T05').component).toBe('ApprovalWorkbenchPage'); + }); +}); diff --git a/frontend/src/shared/ui/screen-types/v2/ApprovalWorkbenchPage.vue.js b/frontend/src/shared/ui/screen-types/v2/ApprovalWorkbenchPage.vue.js new file mode 100644 index 00000000..7d9fdbb1 --- /dev/null +++ b/frontend/src/shared/ui/screen-types/v2/ApprovalWorkbenchPage.vue.js @@ -0,0 +1,99 @@ +import PageLayout from '../../layouts/PageLayout.vue'; +import ReviewWorkbenchLayout from '../../layouts/ReviewWorkbenchLayout.vue'; +import StandardScreenBoundary from './StandardScreenBoundary.vue'; +const props = defineProps(); +const __VLS_emit = defineEmits(); // @ts-ignore +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = PageLayout || PageLayout; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), + asOf: (props.evidence?.asOf), +})); +const __VLS_2 = __VLS_1({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), + asOf: (props.evidence?.asOf), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +{ + const { actions: __VLS_7 } = __VLS_3.slots; + var __VLS_8 = {}; +} +const __VLS_10 = StandardScreenBoundary || StandardScreenBoundary; +// @ts-ignore +const __VLS_11 = __VLS_asFunctionalComponent1(__VLS_10, new __VLS_10({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), +})); +const __VLS_12 = __VLS_11({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), +}, ...__VLS_functionalComponentArgsRest(__VLS_11)); +let __VLS_15; +const __VLS_16 = { + /** @type {typeof __VLS_15.retry} */ + onRetry: (...[$event]) => { + return (__VLS_ctx.$emit('retry')); + // @ts-ignore + [$emit,]; + }, +}; +const { default: __VLS_17 } = __VLS_13.slots; +const __VLS_18 = ReviewWorkbenchLayout || ReviewWorkbenchLayout; +// @ts-ignore +const __VLS_19 = __VLS_asFunctionalComponent1(__VLS_18, new __VLS_18({})); +const __VLS_20 = __VLS_19({}, ...__VLS_functionalComponentArgsRest(__VLS_19)); +const { default: __VLS_23 } = __VLS_21.slots; +{ + const { queue: __VLS_24 } = __VLS_21.slots; + var __VLS_25 = {}; + // @ts-ignore + []; +} +{ + const { detail: __VLS_27 } = __VLS_21.slots; + var __VLS_28 = {}; + // @ts-ignore + []; +} +{ + const { decision: __VLS_30 } = __VLS_21.slots; + var __VLS_31 = {}; + // @ts-ignore + []; +} +// @ts-ignore +[]; +var __VLS_21; +// @ts-ignore +[]; +var __VLS_13; +var __VLS_14; +// @ts-ignore +[]; +var __VLS_3; +// @ts-ignore +var __VLS_9 = __VLS_8, __VLS_26 = __VLS_25, __VLS_29 = __VLS_28, __VLS_32 = __VLS_31; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/screen-types/v2/BatchOperationsPageV2.vue.js b/frontend/src/shared/ui/screen-types/v2/BatchOperationsPageV2.vue.js new file mode 100644 index 00000000..464e6578 --- /dev/null +++ b/frontend/src/shared/ui/screen-types/v2/BatchOperationsPageV2.vue.js @@ -0,0 +1,104 @@ +import PageLayout from '../../layouts/PageLayout.vue'; +import StandardScreenBoundary from './StandardScreenBoundary.vue'; +const props = defineProps(); +const __VLS_emit = defineEmits(); // @ts-ignore +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = PageLayout || PageLayout; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), + asOf: (props.evidence?.asOf), +})); +const __VLS_2 = __VLS_1({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), + asOf: (props.evidence?.asOf), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +{ + const { actions: __VLS_7 } = __VLS_3.slots; + var __VLS_8 = {}; +} +{ + const { summary: __VLS_10 } = __VLS_3.slots; + var __VLS_11 = {}; +} +const __VLS_13 = StandardScreenBoundary || StandardScreenBoundary; +// @ts-ignore +const __VLS_14 = __VLS_asFunctionalComponent1(__VLS_13, new __VLS_13({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), +})); +const __VLS_15 = __VLS_14({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), +}, ...__VLS_functionalComponentArgsRest(__VLS_14)); +let __VLS_18; +const __VLS_19 = { + /** @type {typeof __VLS_18.retry} */ + onRetry: (...[$event]) => { + return (__VLS_ctx.$emit('retry')); + // @ts-ignore + [$emit,]; + }, +}; +const { default: __VLS_20 } = __VLS_16.slots; +__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + ...{ class: "ks-stack" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-stack']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card ks-section" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-card']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-section']} */ ; +var __VLS_21 = {}; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card ks-section" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-card']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-section']} */ ; +var __VLS_23 = {}; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card ks-section" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-card']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-section']} */ ; +var __VLS_25 = {}; +// @ts-ignore +[]; +var __VLS_16; +var __VLS_17; +{ + const { aside: __VLS_27 } = __VLS_3.slots; + var __VLS_28 = {}; + // @ts-ignore + []; +} +// @ts-ignore +[]; +var __VLS_3; +// @ts-ignore +var __VLS_9 = __VLS_8, __VLS_12 = __VLS_11, __VLS_22 = __VLS_21, __VLS_24 = __VLS_23, __VLS_26 = __VLS_25, __VLS_29 = __VLS_28; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/screen-types/v2/DetailReadPage.vue.js b/frontend/src/shared/ui/screen-types/v2/DetailReadPage.vue.js new file mode 100644 index 00000000..d1729b8f --- /dev/null +++ b/frontend/src/shared/ui/screen-types/v2/DetailReadPage.vue.js @@ -0,0 +1,97 @@ +import PageLayout from '../../layouts/PageLayout.vue'; +import StandardScreenBoundary from './StandardScreenBoundary.vue'; +const props = defineProps(); +const __VLS_emit = defineEmits(); // @ts-ignore +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = PageLayout || PageLayout; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), + asOf: (props.evidence?.asOf), + version: (props.evidence?.version), +})); +const __VLS_2 = __VLS_1({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), + asOf: (props.evidence?.asOf), + version: (props.evidence?.version), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +{ + const { actions: __VLS_7 } = __VLS_3.slots; + var __VLS_8 = {}; +} +{ + const { summary: __VLS_10 } = __VLS_3.slots; + var __VLS_11 = {}; +} +const __VLS_13 = StandardScreenBoundary || StandardScreenBoundary; +// @ts-ignore +const __VLS_14 = __VLS_asFunctionalComponent1(__VLS_13, new __VLS_13({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), + staleAt: (props.evidence?.asOf), +})); +const __VLS_15 = __VLS_14({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), + staleAt: (props.evidence?.asOf), +}, ...__VLS_functionalComponentArgsRest(__VLS_14)); +let __VLS_18; +const __VLS_19 = { + /** @type {typeof __VLS_18.retry} */ + onRetry: (...[$event]) => { + return (__VLS_ctx.$emit('retry')); + // @ts-ignore + [$emit,]; + }, +}; +const { default: __VLS_20 } = __VLS_16.slots; +var __VLS_21 = {}; +// @ts-ignore +[]; +var __VLS_16; +var __VLS_17; +if (__VLS_ctx.$slots.evidence) { + { + const { aside: __VLS_23 } = __VLS_3.slots; + var __VLS_24 = {}; + // @ts-ignore + [$slots,]; + } +} +if (__VLS_ctx.$slots.footer) { + { + const { footer: __VLS_26 } = __VLS_3.slots; + var __VLS_27 = {}; + // @ts-ignore + [$slots,]; + } +} +// @ts-ignore +[]; +var __VLS_3; +// @ts-ignore +var __VLS_9 = __VLS_8, __VLS_12 = __VLS_11, __VLS_22 = __VLS_21, __VLS_25 = __VLS_24, __VLS_28 = __VLS_27; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/screen-types/v2/EditFormPage.vue.js b/frontend/src/shared/ui/screen-types/v2/EditFormPage.vue.js new file mode 100644 index 00000000..b4edaaba --- /dev/null +++ b/frontend/src/shared/ui/screen-types/v2/EditFormPage.vue.js @@ -0,0 +1,114 @@ +import PageLayout from '../../layouts/PageLayout.vue'; +import FormPageLayout from '../../layouts/FormPageLayout.vue'; +import StandardScreenBoundary from './StandardScreenBoundary.vue'; +const props = defineProps(); +const __VLS_emit = defineEmits(); // @ts-ignore +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = PageLayout || PageLayout; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), + asOf: (props.evidence?.asOf), + version: (props.evidence?.version), +})); +const __VLS_2 = __VLS_1({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), + asOf: (props.evidence?.asOf), + version: (props.evidence?.version), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +{ + const { actions: __VLS_7 } = __VLS_3.slots; + var __VLS_8 = {}; +} +const __VLS_10 = StandardScreenBoundary || StandardScreenBoundary; +// @ts-ignore +const __VLS_11 = __VLS_asFunctionalComponent1(__VLS_10, new __VLS_10({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), +})); +const __VLS_12 = __VLS_11({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), +}, ...__VLS_functionalComponentArgsRest(__VLS_11)); +let __VLS_15; +const __VLS_16 = { + /** @type {typeof __VLS_15.retry} */ + onRetry: (...[$event]) => { + return (__VLS_ctx.$emit('retry')); + // @ts-ignore + [$emit,]; + }, +}; +const { default: __VLS_17 } = __VLS_13.slots; +const __VLS_18 = FormPageLayout || FormPageLayout; +// @ts-ignore +const __VLS_19 = __VLS_asFunctionalComponent1(__VLS_18, new __VLS_18({ + ...{ 'onSubmit': {} }, +})); +const __VLS_20 = __VLS_19({ + ...{ 'onSubmit': {} }, +}, ...__VLS_functionalComponentArgsRest(__VLS_19)); +let __VLS_23; +const __VLS_24 = { + /** @type {typeof __VLS_23.submit} */ + onSubmit: (...[$event]) => { + return (__VLS_ctx.$emit('submit')); + // @ts-ignore + [$emit,]; + }, +}; +const { default: __VLS_25 } = __VLS_21.slots; +var __VLS_26 = {}; +if (__VLS_ctx.$slots.preview) { + { + const { preview: __VLS_28 } = __VLS_21.slots; + var __VLS_29 = {}; + // @ts-ignore + [$slots,]; + } +} +// @ts-ignore +[]; +var __VLS_21; +var __VLS_22; +// @ts-ignore +[]; +var __VLS_13; +var __VLS_14; +if (__VLS_ctx.$slots.footer) { + { + const { footer: __VLS_31 } = __VLS_3.slots; + var __VLS_32 = {}; + // @ts-ignore + [$slots,]; + } +} +// @ts-ignore +[]; +var __VLS_3; +// @ts-ignore +var __VLS_9 = __VLS_8, __VLS_27 = __VLS_26, __VLS_30 = __VLS_29, __VLS_33 = __VLS_32; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/screen-types/v2/MasterDetailCrudPage.vue.js b/frontend/src/shared/ui/screen-types/v2/MasterDetailCrudPage.vue.js new file mode 100644 index 00000000..61c23386 --- /dev/null +++ b/frontend/src/shared/ui/screen-types/v2/MasterDetailCrudPage.vue.js @@ -0,0 +1,93 @@ +import PageLayout from '../../layouts/PageLayout.vue'; +import StandardScreenBoundary from './StandardScreenBoundary.vue'; +const props = defineProps(); +const __VLS_emit = defineEmits(); // @ts-ignore +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = PageLayout || PageLayout; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), + asOf: (props.evidence?.asOf), + asideWidth: "28rem", +})); +const __VLS_2 = __VLS_1({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), + asOf: (props.evidence?.asOf), + asideWidth: "28rem", +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +{ + const { actions: __VLS_7 } = __VLS_3.slots; + var __VLS_8 = {}; +} +{ + const { filters: __VLS_10 } = __VLS_3.slots; + var __VLS_11 = {}; +} +const __VLS_13 = StandardScreenBoundary || StandardScreenBoundary; +// @ts-ignore +const __VLS_14 = __VLS_asFunctionalComponent1(__VLS_13, new __VLS_13({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), +})); +const __VLS_15 = __VLS_14({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), +}, ...__VLS_functionalComponentArgsRest(__VLS_14)); +let __VLS_18; +const __VLS_19 = { + /** @type {typeof __VLS_18.retry} */ + onRetry: (...[$event]) => { + return (__VLS_ctx.$emit('retry')); + // @ts-ignore + [$emit,]; + }, +}; +const { default: __VLS_20 } = __VLS_16.slots; +var __VLS_21 = {}; +// @ts-ignore +[]; +var __VLS_16; +var __VLS_17; +{ + const { aside: __VLS_23 } = __VLS_3.slots; + var __VLS_24 = {}; + // @ts-ignore + []; +} +if (__VLS_ctx.$slots.footer) { + { + const { footer: __VLS_26 } = __VLS_3.slots; + var __VLS_27 = {}; + // @ts-ignore + [$slots,]; + } +} +// @ts-ignore +[]; +var __VLS_3; +// @ts-ignore +var __VLS_9 = __VLS_8, __VLS_12 = __VLS_11, __VLS_22 = __VLS_21, __VLS_25 = __VLS_24, __VLS_28 = __VLS_27; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/screen-types/v2/ReconciliationExceptionPage.vue.js b/frontend/src/shared/ui/screen-types/v2/ReconciliationExceptionPage.vue.js new file mode 100644 index 00000000..8388b8fe --- /dev/null +++ b/frontend/src/shared/ui/screen-types/v2/ReconciliationExceptionPage.vue.js @@ -0,0 +1,114 @@ +import PageLayout from '../../layouts/PageLayout.vue'; +import StandardScreenBoundary from './StandardScreenBoundary.vue'; +const props = defineProps(); +const __VLS_emit = defineEmits(); // @ts-ignore +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = PageLayout || PageLayout; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), + asOf: (props.evidence?.asOf), + asideWidth: "30rem", +})); +const __VLS_2 = __VLS_1({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), + asOf: (props.evidence?.asOf), + asideWidth: "30rem", +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +{ + const { actions: __VLS_7 } = __VLS_3.slots; + var __VLS_8 = {}; +} +{ + const { filters: __VLS_10 } = __VLS_3.slots; + var __VLS_11 = {}; +} +const __VLS_13 = StandardScreenBoundary || StandardScreenBoundary; +// @ts-ignore +const __VLS_14 = __VLS_asFunctionalComponent1(__VLS_13, new __VLS_13({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), +})); +const __VLS_15 = __VLS_14({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), +}, ...__VLS_functionalComponentArgsRest(__VLS_14)); +let __VLS_18; +const __VLS_19 = { + /** @type {typeof __VLS_18.retry} */ + onRetry: (...[$event]) => { + return (__VLS_ctx.$emit('retry')); + // @ts-ignore + [$emit,]; + }, +}; +const { default: __VLS_20 } = __VLS_16.slots; +var __VLS_21 = {}; +// @ts-ignore +[]; +var __VLS_16; +var __VLS_17; +{ + const { aside: __VLS_23 } = __VLS_3.slots; + __VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + ...{ class: "ks-stack" }, + }); + /** @type {__VLS_StyleScopedClasses['ks-stack']} */ ; + __VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card ks-section" }, + }); + /** @type {__VLS_StyleScopedClasses['ks-card']} */ ; + /** @type {__VLS_StyleScopedClasses['ks-section']} */ ; + var __VLS_24 = {}; + __VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card ks-section" }, + }); + /** @type {__VLS_StyleScopedClasses['ks-card']} */ ; + /** @type {__VLS_StyleScopedClasses['ks-section']} */ ; + var __VLS_26 = {}; + __VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card ks-section" }, + }); + /** @type {__VLS_StyleScopedClasses['ks-card']} */ ; + /** @type {__VLS_StyleScopedClasses['ks-section']} */ ; + var __VLS_28 = {}; + // @ts-ignore + []; +} +if (__VLS_ctx.$slots.footer) { + { + const { footer: __VLS_30 } = __VLS_3.slots; + var __VLS_31 = {}; + // @ts-ignore + [$slots,]; + } +} +// @ts-ignore +[]; +var __VLS_3; +// @ts-ignore +var __VLS_9 = __VLS_8, __VLS_12 = __VLS_11, __VLS_22 = __VLS_21, __VLS_25 = __VLS_24, __VLS_27 = __VLS_26, __VLS_29 = __VLS_28, __VLS_32 = __VLS_31; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/screen-types/v2/ScorecardDashboardPage.vue.js b/frontend/src/shared/ui/screen-types/v2/ScorecardDashboardPage.vue.js new file mode 100644 index 00000000..88e96abe --- /dev/null +++ b/frontend/src/shared/ui/screen-types/v2/ScorecardDashboardPage.vue.js @@ -0,0 +1,117 @@ +import PageLayout from '../../layouts/PageLayout.vue'; +import DashboardLayout from '../../layouts/DashboardLayout.vue'; +import StandardScreenBoundary from './StandardScreenBoundary.vue'; +const props = defineProps(); +const __VLS_emit = defineEmits(); // @ts-ignore +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = PageLayout || PageLayout; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), + asOf: (props.evidence?.asOf), +})); +const __VLS_2 = __VLS_1({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), + asOf: (props.evidence?.asOf), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +{ + const { actions: __VLS_7 } = __VLS_3.slots; + var __VLS_8 = {}; +} +{ + const { filters: __VLS_10 } = __VLS_3.slots; + var __VLS_11 = {}; +} +const __VLS_13 = StandardScreenBoundary || StandardScreenBoundary; +// @ts-ignore +const __VLS_14 = __VLS_asFunctionalComponent1(__VLS_13, new __VLS_13({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), +})); +const __VLS_15 = __VLS_14({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), +}, ...__VLS_functionalComponentArgsRest(__VLS_14)); +let __VLS_18; +const __VLS_19 = { + /** @type {typeof __VLS_18.retry} */ + onRetry: (...[$event]) => { + return (__VLS_ctx.$emit('retry')); + // @ts-ignore + [$emit,]; + }, +}; +const { default: __VLS_20 } = __VLS_16.slots; +const __VLS_21 = DashboardLayout || DashboardLayout; +// @ts-ignore +const __VLS_22 = __VLS_asFunctionalComponent1(__VLS_21, new __VLS_21({})); +const __VLS_23 = __VLS_22({}, ...__VLS_functionalComponentArgsRest(__VLS_22)); +const { default: __VLS_26 } = __VLS_24.slots; +{ + const { kpis: __VLS_27 } = __VLS_24.slots; + var __VLS_28 = {}; + // @ts-ignore + []; +} +{ + const { primary: __VLS_30 } = __VLS_24.slots; + var __VLS_31 = {}; + // @ts-ignore + []; +} +{ + const { secondary: __VLS_33 } = __VLS_24.slots; + var __VLS_34 = {}; + // @ts-ignore + []; +} +{ + const { alerts: __VLS_36 } = __VLS_24.slots; + var __VLS_37 = {}; + // @ts-ignore + []; +} +// @ts-ignore +[]; +var __VLS_24; +// @ts-ignore +[]; +var __VLS_16; +var __VLS_17; +if (__VLS_ctx.$slots.metricDefinitions) { + { + const { aside: __VLS_39 } = __VLS_3.slots; + var __VLS_40 = {}; + // @ts-ignore + [$slots,]; + } +} +// @ts-ignore +[]; +var __VLS_3; +// @ts-ignore +var __VLS_9 = __VLS_8, __VLS_12 = __VLS_11, __VLS_29 = __VLS_28, __VLS_32 = __VLS_31, __VLS_35 = __VLS_34, __VLS_38 = __VLS_37, __VLS_41 = __VLS_40; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/screen-types/v2/SearchListCrudPage.vue.js b/frontend/src/shared/ui/screen-types/v2/SearchListCrudPage.vue.js new file mode 100644 index 00000000..65b4a874 --- /dev/null +++ b/frontend/src/shared/ui/screen-types/v2/SearchListCrudPage.vue.js @@ -0,0 +1,101 @@ +import PageLayout from '../../layouts/PageLayout.vue'; +import StandardScreenBoundary from './StandardScreenBoundary.vue'; +const props = defineProps(); +const __VLS_emit = defineEmits(); // @ts-ignore +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = PageLayout || PageLayout; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), + asOf: (props.evidence?.asOf), + version: (props.evidence?.version), +})); +const __VLS_2 = __VLS_1({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), + asOf: (props.evidence?.asOf), + version: (props.evidence?.version), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +{ + const { actions: __VLS_7 } = __VLS_3.slots; + var __VLS_8 = {}; +} +{ + const { summary: __VLS_10 } = __VLS_3.slots; + var __VLS_11 = {}; +} +{ + const { filters: __VLS_13 } = __VLS_3.slots; + var __VLS_14 = {}; +} +const __VLS_16 = StandardScreenBoundary || StandardScreenBoundary; +// @ts-ignore +const __VLS_17 = __VLS_asFunctionalComponent1(__VLS_16, new __VLS_16({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), + staleAt: (props.evidence?.asOf), +})); +const __VLS_18 = __VLS_17({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), + staleAt: (props.evidence?.asOf), +}, ...__VLS_functionalComponentArgsRest(__VLS_17)); +let __VLS_21; +const __VLS_22 = { + /** @type {typeof __VLS_21.retry} */ + onRetry: (...[$event]) => { + return (__VLS_ctx.$emit('retry')); + // @ts-ignore + [$emit,]; + }, +}; +const { default: __VLS_23 } = __VLS_19.slots; +var __VLS_24 = {}; +// @ts-ignore +[]; +var __VLS_19; +var __VLS_20; +if (__VLS_ctx.$slots.detail) { + { + const { aside: __VLS_26 } = __VLS_3.slots; + var __VLS_27 = {}; + // @ts-ignore + [$slots,]; + } +} +if (__VLS_ctx.$slots.footer) { + { + const { footer: __VLS_29 } = __VLS_3.slots; + var __VLS_30 = {}; + // @ts-ignore + [$slots,]; + } +} +// @ts-ignore +[]; +var __VLS_3; +// @ts-ignore +var __VLS_9 = __VLS_8, __VLS_12 = __VLS_11, __VLS_15 = __VLS_14, __VLS_25 = __VLS_24, __VLS_28 = __VLS_27, __VLS_31 = __VLS_30; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/screen-types/v2/StandardScreenBoundary.vue.js b/frontend/src/shared/ui/screen-types/v2/StandardScreenBoundary.vue.js new file mode 100644 index 00000000..db92bc27 --- /dev/null +++ b/frontend/src/shared/ui/screen-types/v2/StandardScreenBoundary.vue.js @@ -0,0 +1,77 @@ +import QueryStateBoundary from '../../QueryStateBoundary.vue'; +const __VLS_props = withDefaults(defineProps(), { state: 'READY' }); +const emit = defineEmits(); +const __VLS_defaults = { state: 'READY' }; +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = QueryStateBoundary || QueryStateBoundary; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + ...{ 'onRetry': {} }, + loading: (__VLS_ctx.state === 'LOADING'), + processing: (__VLS_ctx.state === 'PROCESSING'), + dirty: (__VLS_ctx.state === 'DIRTY'), + empty: (__VLS_ctx.state === 'EMPTY'), + partial: (__VLS_ctx.state === 'PARTIAL'), + unauthorized: (__VLS_ctx.state === 'UNAUTHORIZED'), + forbidden: (__VLS_ctx.state === 'FORBIDDEN'), + conflict: (__VLS_ctx.state === 'CONFLICT'), + expired: (__VLS_ctx.state === 'EXPIRED'), + readonly: (__VLS_ctx.state === 'READONLY'), + warning: (__VLS_ctx.state === 'WARN' ? __VLS_ctx.warning : undefined), + error: (__VLS_ctx.state === 'ERROR' ? (__VLS_ctx.error ?? new Error('화면 상태 오류')) : undefined), + staleAt: (__VLS_ctx.staleAt), + correlationId: (__VLS_ctx.correlationId), +})); +const __VLS_2 = __VLS_1({ + ...{ 'onRetry': {} }, + loading: (__VLS_ctx.state === 'LOADING'), + processing: (__VLS_ctx.state === 'PROCESSING'), + dirty: (__VLS_ctx.state === 'DIRTY'), + empty: (__VLS_ctx.state === 'EMPTY'), + partial: (__VLS_ctx.state === 'PARTIAL'), + unauthorized: (__VLS_ctx.state === 'UNAUTHORIZED'), + forbidden: (__VLS_ctx.state === 'FORBIDDEN'), + conflict: (__VLS_ctx.state === 'CONFLICT'), + expired: (__VLS_ctx.state === 'EXPIRED'), + readonly: (__VLS_ctx.state === 'READONLY'), + warning: (__VLS_ctx.state === 'WARN' ? __VLS_ctx.warning : undefined), + error: (__VLS_ctx.state === 'ERROR' ? (__VLS_ctx.error ?? new Error('화면 상태 오류')) : undefined), + staleAt: (__VLS_ctx.staleAt), + correlationId: (__VLS_ctx.correlationId), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +let __VLS_5; +const __VLS_6 = { + /** @type {typeof __VLS_5.retry} */ + onRetry: (...[$event]) => { + return (__VLS_ctx.emit('retry')); + // @ts-ignore + [state, state, state, state, state, state, state, state, state, state, state, state, warning, error, staleAt, correlationId, emit,]; + }, +}; +var __VLS_7; +const { default: __VLS_8 } = __VLS_3.slots; +var __VLS_9 = {}; +// @ts-ignore +[]; +var __VLS_3; +var __VLS_4; +// @ts-ignore +var __VLS_10 = __VLS_9; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, + props: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/screen-types/v2/StepWizardPage.vue.js b/frontend/src/shared/ui/screen-types/v2/StepWizardPage.vue.js new file mode 100644 index 00000000..b35532f1 --- /dev/null +++ b/frontend/src/shared/ui/screen-types/v2/StepWizardPage.vue.js @@ -0,0 +1,123 @@ +import PageLayout from '../../layouts/PageLayout.vue'; +import StandardScreenBoundary from './StandardScreenBoundary.vue'; +const props = defineProps(); +const __VLS_emit = defineEmits(); // @ts-ignore +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = PageLayout || PageLayout; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), +})); +const __VLS_2 = __VLS_1({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +{ + const { summary: __VLS_7 } = __VLS_3.slots; + __VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card ks-wizard-progress" }, + role: "status", + }); + /** @type {__VLS_StyleScopedClasses['ks-card']} */ ; + /** @type {__VLS_StyleScopedClasses['ks-wizard-progress']} */ ; + (__VLS_ctx.currentStep); + (__VLS_ctx.totalSteps); + __VLS_asFunctionalElement1(__VLS_intrinsics.progress)({ + value: (__VLS_ctx.currentStep), + max: (__VLS_ctx.totalSteps), + }); + // @ts-ignore + [currentStep, currentStep, totalSteps, totalSteps,]; +} +const __VLS_8 = StandardScreenBoundary || StandardScreenBoundary; +// @ts-ignore +const __VLS_9 = __VLS_asFunctionalComponent1(__VLS_8, new __VLS_8({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), +})); +const __VLS_10 = __VLS_9({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), +}, ...__VLS_functionalComponentArgsRest(__VLS_9)); +let __VLS_13; +const __VLS_14 = { + /** @type {typeof __VLS_13.retry} */ + onRetry: (...[$event]) => { + return (__VLS_ctx.$emit('retry')); + // @ts-ignore + [$emit,]; + }, +}; +const { default: __VLS_15 } = __VLS_11.slots; +var __VLS_16 = {}; +// @ts-ignore +[]; +var __VLS_11; +var __VLS_12; +{ + const { footer: __VLS_18 } = __VLS_3.slots; + var __VLS_19 = {}; + __VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({ + ...{ onClick: (...[$event]) => { + return (__VLS_ctx.$emit('previous')); + // @ts-ignore + [$emit,]; + } }, + type: "button", + disabled: (__VLS_ctx.currentStep <= 1), + }); + if (__VLS_ctx.currentStep < __VLS_ctx.totalSteps) { + __VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({ + ...{ onClick: (...[$event]) => { + if (!(__VLS_ctx.currentStep < __VLS_ctx.totalSteps)) + throw 0; + return (__VLS_ctx.$emit('next')); + // @ts-ignore + [currentStep, currentStep, totalSteps, $emit,]; + } }, + type: "button", + }); + } + else { + __VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({ + ...{ onClick: (...[$event]) => { + if (!!(__VLS_ctx.currentStep < __VLS_ctx.totalSteps)) + throw 0; + return (__VLS_ctx.$emit('finish')); + // @ts-ignore + [$emit,]; + } }, + type: "button", + }); + } + // @ts-ignore + []; +} +// @ts-ignore +[]; +var __VLS_3; +// @ts-ignore +var __VLS_17 = __VLS_16, __VLS_20 = __VLS_19; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/screen-types/v2/VersionGovernancePage.vue.js b/frontend/src/shared/ui/screen-types/v2/VersionGovernancePage.vue.js new file mode 100644 index 00000000..627f2f2e --- /dev/null +++ b/frontend/src/shared/ui/screen-types/v2/VersionGovernancePage.vue.js @@ -0,0 +1,125 @@ +import PageLayout from '../../layouts/PageLayout.vue'; +import StandardScreenBoundary from './StandardScreenBoundary.vue'; +const props = defineProps(); +const __VLS_emit = defineEmits(); // @ts-ignore +const __VLS_ctx = { + ...{}, + ...{}, + ...{}, + ...{}, +}; +let __VLS_components; +let __VLS_intrinsics; +let __VLS_directives; +const __VLS_0 = PageLayout || PageLayout; +// @ts-ignore +const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), + asOf: (props.evidence?.asOf), + version: (props.evidence?.version), + asideWidth: "28rem", +})); +const __VLS_2 = __VLS_1({ + title: (props.title), + subtitle: (props.subtitle), + status: (props.state), + asOf: (props.evidence?.asOf), + version: (props.evidence?.version), + asideWidth: "28rem", +}, ...__VLS_functionalComponentArgsRest(__VLS_1)); +var __VLS_5; +const { default: __VLS_6 } = __VLS_3.slots; +{ + const { actions: __VLS_7 } = __VLS_3.slots; + var __VLS_8 = {}; +} +{ + const { summary: __VLS_10 } = __VLS_3.slots; + var __VLS_11 = {}; +} +const __VLS_13 = StandardScreenBoundary || StandardScreenBoundary; +// @ts-ignore +const __VLS_14 = __VLS_asFunctionalComponent1(__VLS_13, new __VLS_13({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), +})); +const __VLS_15 = __VLS_14({ + ...{ 'onRetry': {} }, + state: (props.state), + warning: (props.warning), +}, ...__VLS_functionalComponentArgsRest(__VLS_14)); +let __VLS_18; +const __VLS_19 = { + /** @type {typeof __VLS_18.retry} */ + onRetry: (...[$event]) => { + return (__VLS_ctx.$emit('retry')); + // @ts-ignore + [$emit,]; + }, +}; +const { default: __VLS_20 } = __VLS_16.slots; +__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + ...{ class: "ks-stack" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-stack']} */ ; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card ks-section" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-card']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-section']} */ ; +var __VLS_21 = {}; +__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card ks-section" }, +}); +/** @type {__VLS_StyleScopedClasses['ks-card']} */ ; +/** @type {__VLS_StyleScopedClasses['ks-section']} */ ; +var __VLS_23 = {}; +// @ts-ignore +[]; +var __VLS_16; +var __VLS_17; +{ + const { aside: __VLS_25 } = __VLS_3.slots; + __VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({ + ...{ class: "ks-stack" }, + }); + /** @type {__VLS_StyleScopedClasses['ks-stack']} */ ; + __VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card ks-section" }, + }); + /** @type {__VLS_StyleScopedClasses['ks-card']} */ ; + /** @type {__VLS_StyleScopedClasses['ks-section']} */ ; + var __VLS_26 = {}; + __VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({ + ...{ class: "ks-card ks-section" }, + }); + /** @type {__VLS_StyleScopedClasses['ks-card']} */ ; + /** @type {__VLS_StyleScopedClasses['ks-section']} */ ; + var __VLS_28 = {}; + // @ts-ignore + []; +} +if (__VLS_ctx.$slots.footer) { + { + const { footer: __VLS_30 } = __VLS_3.slots; + var __VLS_31 = {}; + // @ts-ignore + [$slots,]; + } +} +// @ts-ignore +[]; +var __VLS_3; +// @ts-ignore +var __VLS_9 = __VLS_8, __VLS_12 = __VLS_11, __VLS_22 = __VLS_21, __VLS_24 = __VLS_23, __VLS_27 = __VLS_26, __VLS_29 = __VLS_28, __VLS_32 = __VLS_31; +// @ts-ignore +[]; +const __VLS_base = (await import('vue')).defineComponent({ + __typeEmits: {}, + __typeProps: {}, +}); +const __VLS_export = {}; +export default {}; diff --git a/frontend/src/shared/ui/screen-types/v2/index.js b/frontend/src/shared/ui/screen-types/v2/index.js new file mode 100644 index 00000000..2b93c4e3 --- /dev/null +++ b/frontend/src/shared/ui/screen-types/v2/index.js @@ -0,0 +1,11 @@ +export { default as SearchListCrudPage } from './SearchListCrudPage.vue'; +export { default as DetailReadPage } from './DetailReadPage.vue'; +export { default as EditFormPage } from './EditFormPage.vue'; +export { default as MasterDetailCrudPage } from './MasterDetailCrudPage.vue'; +export { default as ApprovalWorkbenchPage } from './ApprovalWorkbenchPage.vue'; +export { default as StepWizardPage } from './StepWizardPage.vue'; +export { default as ScorecardDashboardPage } from './ScorecardDashboardPage.vue'; +export { default as BatchOperationsPageV2 } from './BatchOperationsPageV2.vue'; +export { default as ReconciliationExceptionPage } from './ReconciliationExceptionPage.vue'; +export { default as VersionGovernancePage } from './VersionGovernancePage.vue'; +export { default as StandardScreenBoundary } from './StandardScreenBoundary.vue'; diff --git a/frontend/src/shared/ui/tests/adapterCompatibility.spec.js b/frontend/src/shared/ui/tests/adapterCompatibility.spec.js new file mode 100644 index 00000000..f1e1cdb1 --- /dev/null +++ b/frontend/src/shared/ui/tests/adapterCompatibility.spec.js @@ -0,0 +1,11 @@ +import { describe, expect, it } from 'vitest'; +import { evaluateUiAdapterCompatibility } from '../adapter/compatibility'; +import { requiredUiAdapterCapabilities } from '../adapter/contracts'; +const component = {}; +function adapter(overrides = {}) { + return { descriptor: { id: 'native-accessible', version: '1', contractVersion: '4.0', vendor: 'native', capabilities: new Set(requiredUiAdapterCapabilities), productionEligible: false, accessibilityBaseline: 'WCAG_2_2_AA_TARGET', ...overrides }, components: { Button: component, TextField: component, TextArea: component, Select: component, MultiSelect: component, Checkbox: component, DateField: component, NumberField: component, Dialog: component, StatusTag: component, InlineMessage: component, Paginator: component, Tabs: component, DataGrid: component } }; +} +describe('UI adapter v4 compatibility', () => { + it('accepts the complete reference provider', () => expect(evaluateUiAdapterCompatibility(adapter(), requiredUiAdapterCapabilities).compatible).toBe(true)); + it('rejects a provider with a missing capability', () => expect(evaluateUiAdapterCompatibility(adapter({ capabilities: new Set(['button']) }), requiredUiAdapterCapabilities).compatible).toBe(false)); +}); diff --git a/frontend/src/shared/ui/tests/idempotency.spec.js b/frontend/src/shared/ui/tests/idempotency.spec.js new file mode 100644 index 00000000..dac1d6f2 --- /dev/null +++ b/frontend/src/shared/ui/tests/idempotency.spec.js @@ -0,0 +1,9 @@ +import { describe, expect, it } from 'vitest'; +import { createIdempotentCommand } from '../../commands/idempotency'; +describe('idempotent command envelope', () => { + it('keeps one key for retries of the same envelope', () => { + const command = createIdempotentCommand({ value: 1 }); + const retry = command; + expect(retry.idempotencyKey).toBe(command.idempotencyKey); + }); +}); diff --git a/frontend/tsconfig.tsbuildinfo b/frontend/tsconfig.tsbuildinfo new file mode 100644 index 00000000..45a25934 --- /dev/null +++ b/frontend/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/env.d.ts","./src/main.ts","./src/app/queryclient.ts","./src/app/router.ts","./src/features/data-quality/schema.ts","./src/features/data-quality/tests/schema.spec.ts","./src/features/model-operations/api.ts","./src/features/model-operations/queries.ts","./src/features/model-operations/schema.ts","./src/features/model-operations/tests/schema.spec.ts","./src/features/sell-decision/api.ts","./src/features/sell-decision/queries.ts","./src/features/sell-decision/schema.ts","./src/features/sell-decision/tests/schema.spec.ts","./src/shared/api/client.ts","./src/shared/api/problem.ts","./src/shared/commands/idempotency.ts","./src/shared/contracts/versionset.ts","./src/shared/crud/contracts.ts","./src/shared/crud/querycodec.ts","./src/shared/crud/resourcedefinition.ts","./src/shared/crud/usecrudliststate.ts","./src/shared/crud/useoptimisticcommand.ts","./src/shared/crud/tests/querycodec.spec.ts","./src/shared/formatters/financial.ts","./src/shared/formatters/tests/financial.spec.ts","./src/shared/ui/index.ts","./src/shared/ui/adapter/compatibility.ts","./src/shared/ui/adapter/contracts.ts","./src/shared/ui/adapter/useuiadapter.ts","./src/shared/ui/adapter/native/index.ts","./src/shared/ui/adapter/native/installnativeadapter.ts","./src/shared/ui/adapter/primevue/index.ts","./src/shared/ui/adapter/primevue/installprimevueadapter.ts","./src/shared/ui/adapter/tests/uiadapter.contract.spec.ts","./src/shared/ui/components/index.ts","./src/shared/ui/contracts/screencontract.ts","./src/shared/ui/layouts/index.ts","./src/shared/ui/provider/uiprovider.ts","./src/shared/ui/provider/index.ts","./src/shared/ui/provider/resolveuiprovider.ts","./src/shared/ui/screen-types/catalogue.ts","./src/shared/ui/screen-types/index.ts","./src/shared/ui/screen-types/tests/catalogue.spec.ts","./src/shared/ui/screen-types/v2/index.ts","./src/shared/ui/tests/adaptercompatibility.spec.ts","./src/shared/ui/tests/idempotency.spec.ts","./src/app.vue","./src/features/data-quality/pages/dataqualitypage.vue","./src/features/model-operations/components/automationboundarypanel.vue","./src/features/model-operations/components/modeloperationtable.vue","./src/features/model-operations/pages/modeloperationspage.vue","./src/features/sell-decision/components/policytracepanel.vue","./src/features/sell-decision/pages/selldecisionpage.vue","./src/features/ui-standard/pages/uistandardpage.vue","./src/shared/auth/permissionguard.vue","./src/shared/crud/standardcrudformpage.vue","./src/shared/crud/standardcrudlistpage.vue","./src/shared/status/datafreshnessbadge.vue","./src/shared/ui/datagridshell.vue","./src/shared/ui/evidenceversionset.vue","./src/shared/ui/querystateboundary.vue","./src/shared/ui/versionconflictdialog.vue","./src/shared/ui/adapter/native/nativebuttonadapter.vue","./src/shared/ui/adapter/native/nativecheckboxadapter.vue","./src/shared/ui/adapter/native/nativedatagridadapter.vue","./src/shared/ui/adapter/native/nativedatefieldadapter.vue","./src/shared/ui/adapter/native/nativedialogadapter.vue","./src/shared/ui/adapter/native/nativeinlinemessageadapter.vue","./src/shared/ui/adapter/native/nativemultiselectadapter.vue","./src/shared/ui/adapter/native/nativenumberfieldadapter.vue","./src/shared/ui/adapter/native/nativepaginatoradapter.vue","./src/shared/ui/adapter/native/nativeselectadapter.vue","./src/shared/ui/adapter/native/nativestatustagadapter.vue","./src/shared/ui/adapter/native/nativetabsadapter.vue","./src/shared/ui/adapter/native/nativetextareaadapter.vue","./src/shared/ui/adapter/native/nativetextfieldadapter.vue","./src/shared/ui/adapter/primevue/aggridadapter.vue","./src/shared/ui/adapter/primevue/primebuttonadapter.vue","./src/shared/ui/adapter/primevue/primecheckboxadapter.vue","./src/shared/ui/adapter/primevue/primedatefieldadapter.vue","./src/shared/ui/adapter/primevue/primedialogadapter.vue","./src/shared/ui/adapter/primevue/primeinlinemessageadapter.vue","./src/shared/ui/adapter/primevue/primemultiselectadapter.vue","./src/shared/ui/adapter/primevue/primenumberfieldadapter.vue","./src/shared/ui/adapter/primevue/primepaginatoradapter.vue","./src/shared/ui/adapter/primevue/primeselectadapter.vue","./src/shared/ui/adapter/primevue/primestatustagadapter.vue","./src/shared/ui/adapter/primevue/primetabsadapter.vue","./src/shared/ui/adapter/primevue/primetextareaadapter.vue","./src/shared/ui/adapter/primevue/primetextfieldadapter.vue","./src/shared/ui/components/fieldshell.vue","./src/shared/ui/components/ksbutton.vue","./src/shared/ui/components/kscheckbox.vue","./src/shared/ui/components/kscommandbar.vue","./src/shared/ui/components/ksdatacontextheader.vue","./src/shared/ui/components/ksdatagrid.vue","./src/shared/ui/components/ksdatefield.vue","./src/shared/ui/components/ksdialog.vue","./src/shared/ui/components/ksinlinemessage.vue","./src/shared/ui/components/ksmultiselect.vue","./src/shared/ui/components/ksnumberfield.vue","./src/shared/ui/components/kspaginator.vue","./src/shared/ui/components/ksselect.vue","./src/shared/ui/components/ksstatustag.vue","./src/shared/ui/components/kstabs.vue","./src/shared/ui/components/kstextarea.vue","./src/shared/ui/components/kstextfield.vue","./src/shared/ui/feedback/standardstatepanel.vue","./src/shared/ui/layouts/appshelllayout.vue","./src/shared/ui/layouts/crudworkspacelayout.vue","./src/shared/ui/layouts/dashboardlayout.vue","./src/shared/ui/layouts/formpagelayout.vue","./src/shared/ui/layouts/operationsconsolelayout.vue","./src/shared/ui/layouts/pagelayout.vue","./src/shared/ui/layouts/reviewworkbenchlayout.vue","./src/shared/ui/screen-types/batchoperationspage.vue","./src/shared/ui/screen-types/cruddetailpage.vue","./src/shared/ui/screen-types/crudformpage.vue","./src/shared/ui/screen-types/crudlistpage.vue","./src/shared/ui/screen-types/crudreviewpage.vue","./src/shared/ui/screen-types/v2/approvalworkbenchpage.vue","./src/shared/ui/screen-types/v2/batchoperationspagev2.vue","./src/shared/ui/screen-types/v2/detailreadpage.vue","./src/shared/ui/screen-types/v2/editformpage.vue","./src/shared/ui/screen-types/v2/masterdetailcrudpage.vue","./src/shared/ui/screen-types/v2/reconciliationexceptionpage.vue","./src/shared/ui/screen-types/v2/scorecarddashboardpage.vue","./src/shared/ui/screen-types/v2/searchlistcrudpage.vue","./src/shared/ui/screen-types/v2/standardscreenboundary.vue","./src/shared/ui/screen-types/v2/stepwizardpage.vue","./src/shared/ui/screen-types/v2/versiongovernancepage.vue","./vite.config.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 00000000..2031b4b7 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,8 @@ +import { fileURLToPath, URL } from 'node:url'; +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +export default defineConfig({ + plugins: [vue()], + resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } }, + server: { proxy: { '/api': 'http://localhost:5000' } } +}); diff --git a/src/KArtSell.DbMigrator/Program.cs b/src/KArtSell.DbMigrator/Program.cs index da201e2a..e8160cee 100644 --- a/src/KArtSell.DbMigrator/Program.cs +++ b/src/KArtSell.DbMigrator/Program.cs @@ -8,12 +8,18 @@ if (!Directory.Exists(scriptsPath)) { scriptsPath = Path.Combine(AppContext.BaseDirectory, "migrations"); } +if (!Directory.Exists(scriptsPath)) +{ + // Fallback: SQL files are copied to bin root in flattened structure + scriptsPath = AppContext.BaseDirectory; +} EnsureDatabase.For.PostgresqlDatabase(connectionString); var result = DeployChanges.To .PostgresqlDatabase(connectionString) .WithScriptsFromFileSystem(scriptsPath) + .WithVariables(new Dictionary()) .JournalToPostgresqlTable("public", "kartsell_schema_versions") .LogToConsole() .Build()