기능구현중

This commit is contained in:
2026-01-11 13:47:33 +09:00
parent f5bf084afc
commit d56154d5d2
13 changed files with 948 additions and 46 deletions

View File

@@ -130,7 +130,7 @@
</div>
<!-- PM/PL 이력 -->
<div class="card">
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="bi bi-person-badge me-2"></i>PM/PL 담당 이력</span>
<button class="btn btn-sm btn-primary" @click="showAssignModal = true">
@@ -161,6 +161,44 @@
</table>
</div>
</div>
<!-- 저장소 관리 -->
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="bi bi-git me-2"></i>저장소 관리</span>
<button class="btn btn-sm btn-primary" @click="openRepoModal()">
<i class="bi bi-plus"></i> 저장소 추가
</button>
</div>
<div class="table-responsive">
<table class="table table-sm table-bordered mb-0">
<thead class="table-light">
<tr>
<th style="width: 70px">타입</th>
<th>저장소명</th>
<th>경로</th>
<th style="width: 100px">브랜치</th>
<th style="width: 100px">작업</th>
</tr>
</thead>
<tbody>
<tr v-for="r in repositories" :key="r.repoId">
<td><span :class="r.serverType === 'GIT' ? 'badge bg-dark' : 'badge bg-warning text-dark'">{{ r.serverType }}</span></td>
<td>{{ r.repoName }}</td>
<td><small class="text-muted">{{ r.serverName }} &gt;</small> {{ r.repoPath }}</td>
<td>{{ r.defaultBranch || '-' }}</td>
<td>
<button class="btn btn-sm btn-outline-secondary me-1" @click="openRepoModal(r)" title="수정"><i class="bi bi-pencil"></i></button>
<button class="btn btn-sm btn-outline-danger" @click="deleteRepo(r.repoId)" title="삭제"><i class="bi bi-trash"></i></button>
</td>
</tr>
<tr v-if="repositories.length === 0">
<td colspan="5" class="text-center text-muted py-3">등록된 저장소가 없습니다.</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-4">
@@ -237,6 +275,55 @@
</div>
</div>
<div class="modal-backdrop fade show" v-if="showAssignModal"></div>
<!-- 저장소 추가/수정 모달 -->
<div class="modal fade" :class="{ show: showRepoModal }" :style="{ display: showRepoModal ? 'block' : 'none' }">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ repoForm.repoId ? '저장소 수정' : '저장소 추가' }}</h5>
<button type="button" class="btn-close" @click="showRepoModal = false"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">VCS 서버 <span class="text-danger">*</span></label>
<select class="form-select" v-model="repoForm.serverId">
<option value="">선택하세요</option>
<option v-for="s in vcsServers" :key="s.serverId" :value="s.serverId">[{{ s.serverType }}] {{ s.serverName }}</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">저장소명</label>
<input type="text" class="form-control" v-model="repoForm.repoName" placeholder="표시용 이름" />
</div>
<div class="mb-3">
<label class="form-label">저장소 경로 <span class="text-danger">*</span></label>
<input type="text" class="form-control" v-model="repoForm.repoPath" placeholder="/owner/repo.git 또는 /svn/project/trunk" />
</div>
<div class="mb-3">
<label class="form-label">전체 URL</label>
<input type="text" class="form-control" v-model="repoForm.repoUrl" placeholder="https://github.com/owner/repo.git" />
</div>
<div class="mb-3">
<label class="form-label">기본 브랜치 (Git)</label>
<input type="text" class="form-control" v-model="repoForm.defaultBranch" placeholder="main" />
</div>
<div class="mb-3">
<label class="form-label">설명</label>
<textarea class="form-control" v-model="repoForm.description" rows="2"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="showRepoModal = false">취소</button>
<button type="button" class="btn btn-primary" @click="saveRepo" :disabled="isSavingRepo">
<span v-if="isSavingRepo"><span class="spinner-border spinner-border-sm me-1"></span></span>
<i class="bi bi-check-lg me-1" v-else></i>저장
</button>
</div>
</div>
</div>
</div>
<div class="modal-backdrop fade show" v-if="showRepoModal"></div>
</div>
</template>
@@ -275,10 +362,25 @@ const assignForm = ref({
changeReason: ''
})
// 저장소 관련
const repositories = ref<any[]>([])
const vcsServers = ref<any[]>([])
const showRepoModal = ref(false)
const isSavingRepo = ref(false)
const repoForm = ref({
repoId: null as number | null,
serverId: '',
repoName: '',
repoPath: '',
repoUrl: '',
defaultBranch: 'main',
description: ''
})
onMounted(async () => {
const user = await fetchCurrentUser()
if (!user) { router.push('/login'); return }
await Promise.all([loadProject(), loadBusinesses(), loadEmployees()])
await Promise.all([loadProject(), loadBusinesses(), loadEmployees(), loadVcsServers(), loadRepositories()])
})
async function loadProject() {
@@ -310,6 +412,68 @@ async function loadEmployees() {
} catch (e) { console.error(e) }
}
async function loadVcsServers() {
try {
const res = await $fetch<{ servers: any[] }>('/api/vcs-server/list')
vcsServers.value = res.servers || []
} catch (e) { console.error(e) }
}
async function loadRepositories() {
try {
const res = await $fetch<{ repositories: any[] }>(`/api/repository/list?projectId=${route.params.id}`)
repositories.value = res.repositories || []
} catch (e) { console.error(e) }
}
function openRepoModal(repo?: any) {
if (repo) {
repoForm.value = {
repoId: repo.repoId,
serverId: repo.serverId?.toString() || '',
repoName: repo.repoName || '',
repoPath: repo.repoPath || '',
repoUrl: repo.repoUrl || '',
defaultBranch: repo.defaultBranch || 'main',
description: repo.description || ''
}
} else {
repoForm.value = { repoId: null, serverId: '', repoName: '', repoPath: '', repoUrl: '', defaultBranch: 'main', description: '' }
}
showRepoModal.value = true
}
async function saveRepo() {
if (!repoForm.value.serverId || !repoForm.value.repoPath) {
alert('VCS 서버와 저장소 경로는 필수입니다.')
return
}
isSavingRepo.value = true
try {
if (repoForm.value.repoId) {
await $fetch(`/api/repository/${repoForm.value.repoId}`, { method: 'PUT', body: repoForm.value })
} else {
await $fetch('/api/repository/create', { method: 'POST', body: { ...repoForm.value, projectId: Number(route.params.id) } })
}
showRepoModal.value = false
await loadRepositories()
} catch (e: any) {
alert(e.data?.message || '저장에 실패했습니다.')
} finally {
isSavingRepo.value = false
}
}
async function deleteRepo(repoId: number) {
if (!confirm('이 저장소를 삭제하시겠습니까?')) return
try {
await $fetch(`/api/repository/${repoId}`, { method: 'DELETE' })
await loadRepositories()
} catch (e: any) {
alert(e.data?.message || '삭제에 실패했습니다.')
}
}
function toggleEdit() {
if (!isEditing.value) {
form.value = {