feat: complete release preparation, 10+ ad mediation, CI/CD, and docker deployment
Some checks failed
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / deploy (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Release & Packaging Pipeline / Build & Publish Admin Docker Image (push) Failing after 8s
Release & Code Signing CA Pipeline / build-and-sign-windows (push) Failing after 1m51s
Build macOS / Build & Package (macOS) (push) Failing after 4s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
Release & Code Signing CA Pipeline / build-and-sign-macos (push) Failing after 3s
Release & Packaging Pipeline / Package macOS Desktop App (push) Failing after 4s
Release & Packaging Pipeline / Package Windows Desktop App (push) Failing after 2m28s
Release & Packaging Pipeline / Publish Official GitHub Release (push) Has been skipped

This commit is contained in:
Yun Chan 2026-08-20 11:12:05 +09:00
parent 5cd1de6859
commit 708e20f747
406 changed files with 42464 additions and 6199 deletions

View file

@ -1,7 +1,7 @@
// src/renderer/components/TemplateSection.tsx
// Phase 12.3: 딕테이션 템플릿 관리 UI (CommandsPage 내 섹션)
import { useState, useEffect, useCallback } from 'react'
import { useState, useEffect, useCallback, useRef } from 'react'
import { Box, Button, IconButton, Dialog, DialogTitle, DialogContent, DialogActions, TextField, Tooltip } from '@mui/material'
import { Plus, Trash2, Pencil, Play } from 'lucide-react'
import { MetalCard, PhosphorText, Led, PhysicalButton } from '@d3ro/ui/components/ds'
@ -22,6 +22,10 @@ export function TemplateSection(): React.ReactElement {
const [formFields, setFormFields] = useState<TemplateField[]>([])
const [session, setSession] = useState<TemplateSessionInfo | null>(null)
const [saving, setSaving] = useState(false)
const isSavingRef = useRef(false)
const isDeletingRef = useRef(false)
const loadData = useCallback(async () => {
setLoading(true)
const result = await window.electronAPI.dictationTemplate.getAll()
@ -45,6 +49,7 @@ export function TemplateSection(): React.ReactElement {
}, [loadData])
const openCreate = () => {
if (dialogOpen) return
setEditId(null)
setFormName('')
setFormDesc('')
@ -54,6 +59,7 @@ export function TemplateSection(): React.ReactElement {
}
const openEdit = (template: DictationTemplate) => {
if (dialogOpen) return
setEditId(template.id)
setFormName(template.name)
setFormDesc(template.description)
@ -63,29 +69,43 @@ export function TemplateSection(): React.ReactElement {
}
const handleSave = async () => {
if (editId) {
await window.electronAPI.dictationTemplate.update({
id: editId,
name: formName.trim(),
description: formDesc.trim(),
fields: formFields,
outputFormat: formOutput,
})
} else {
await window.electronAPI.dictationTemplate.create({
name: formName.trim(),
description: formDesc.trim(),
fields: formFields,
outputFormat: formOutput,
})
if (!formName.trim() || formFields.length === 0 || saving || isSavingRef.current) return
isSavingRef.current = true
setSaving(true)
try {
if (editId) {
await window.electronAPI.dictationTemplate.update({
id: editId,
name: formName.trim(),
description: formDesc.trim(),
fields: formFields,
outputFormat: formOutput,
})
} else {
await window.electronAPI.dictationTemplate.create({
name: formName.trim(),
description: formDesc.trim(),
fields: formFields,
outputFormat: formOutput,
})
}
setDialogOpen(false)
loadData()
} finally {
setSaving(false)
isSavingRef.current = false
}
setDialogOpen(false)
loadData()
}
const handleDelete = async (id: string) => {
await window.electronAPI.dictationTemplate.delete({ id })
loadData()
if (isDeletingRef.current) return
isDeletingRef.current = true
try {
await window.electronAPI.dictationTemplate.delete({ id })
loadData()
} finally {
isDeletingRef.current = false
}
}
const handleStartSession = async (templateId: string) => {
@ -281,8 +301,8 @@ export function TemplateSection(): React.ReactElement {
<Button onClick={() => setDialogOpen(false)} sx={{ color: d3roPalette.text.inactive }}>
{t('common.cancel')}
</Button>
<Button onClick={handleSave} variant="contained" disabled={!formName.trim() || formFields.length === 0}>
{t('common.save')}
<Button onClick={handleSave} variant="contained" disabled={!formName.trim() || formFields.length === 0 || saving}>
{saving ? t('common.saving') || '저장 중...' : t('common.save')}
</Button>
</DialogActions>
</Dialog>