redmine-workflow-engine/app/views/issues/_workflow_status.html.erb
ioresponse e67fb92189 Initial commit: Redmine Workflow Engine Plugin
Features:
- Custom workflow creation per project/tracker
- Step-by-step workflow definition
- Assignees per step (user, role group, department)
- Next/Previous step navigation
- Reject to first step
- Skip step (admin only)
- Step deadline settings
- Workflow dashboard
- Group member selection when proceeding

🤖 Generated with Claude Code
2025-12-23 00:16:43 +09:00

285 lines
12 KiB
Plaintext

<%
workflow_state = IssueWorkflowState.find_by(issue_id: @issue.id)
unless workflow_state
# 이슈에 적용 가능한 워크플로우 찾기
workflow = CustomWorkflow.find_for_issue(@issue)
if workflow && workflow.start_step
workflow_state = IssueWorkflowState.create(
issue: @issue,
custom_workflow: workflow,
current_step: workflow.start_step,
started_at: Time.current
)
end
end
%>
<% if workflow_state && workflow_state.custom_workflow %>
<%
steps = workflow_state.custom_workflow.workflow_steps.ordered
current_step = workflow_state.current_step
%>
<div class="box" style="margin-top: 15px; margin-bottom: 15px; background: #f8f9fa;">
<h3 style="margin-top: 0; margin-bottom: 10px; font-size: 14px;">
<span style="color: #007bff;">&#9654;</span>
워크플로우: <%= workflow_state.custom_workflow.name %>
</h3>
<div style="display: flex; align-items: center; flex-wrap: wrap; gap: 5px; margin: 10px 0;">
<% steps.each_with_index do |step, idx| %>
<%
is_current = current_step && current_step.id == step.id
is_completed = current_step && step.position < current_step.position
%>
<div style="
padding: 6px 12px;
border-radius: 4px;
font-size: 12px;
<% if is_current %>
background: #007bff;
color: white;
font-weight: bold;
<% elsif is_completed %>
background: #28a745;
color: white;
<% else %>
background: #e9ecef;
color: #666;
<% end %>
">
<%= step.name %>
</div>
<% if idx < steps.length - 1 %>
<span style="color: #999; font-size: 14px;">&rarr;</span>
<% end %>
<% end %>
</div>
<% if current_step %>
<div style="font-size: 12px; margin-top: 10px;">
<strong>현재 단계:</strong> <%= current_step.name %>
<% if current_step.workflow_step_assignees.any? %>
&nbsp;|&nbsp;
<strong>담당자:</strong>
<% current_step.workflow_step_assignees.each do |assignee| %>
<span style="background: #fff; border: 1px solid #ddd; padding: 1px 6px; border-radius: 3px; margin-left: 3px; font-size: 11px;">
<%= assignee.assignee_name %>
</span>
<% end %>
<% end %>
<% if current_step.is_start && @issue.editable?(User.current) %>
&nbsp;|&nbsp;
<a href="/issues/<%= @issue.id %>/edit" target="_blank" class="icon icon-edit" style="color: #007bff;">이슈 편집</a>
<% end %>
</div>
<%
# 다음 단계 정보 확인
next_step = current_step.next_step
next_step_has_group = next_step && next_step.workflow_step_assignees.where(assignee_type: ['role_group', 'department']).any?
next_step_group_members = next_step_has_group ? next_step.all_assignee_users : []
%>
<div style="margin-top: 12px;">
<% unless current_step.is_end %>
<% if workflow_state.can_proceed?(User.current) %>
<% if next_step_has_group && next_step_group_members.count > 1 %>
<a href="#" onclick="showNextStepModal(); return false;"
class="button button-positive"
style="font-size: 11px; padding: 4px 10px;">
다음 단계로 &rarr;
</a>
<% else %>
<%= link_to '다음 단계로 &rarr;'.html_safe,
issue_workflow_next_step_path(@issue),
method: :post,
class: 'button button-positive',
style: 'font-size: 11px; padding: 4px 10px;',
data: { confirm: '다음 단계로 진행하시겠습니까?' } %>
<% end %>
<% else %>
<span class="button" style="font-size: 11px; padding: 4px 10px; opacity: 0.5; cursor: not-allowed;">
다음 단계로 (권한 없음)
</span>
<% end %>
<% else %>
<span style="color: #28a745; font-weight: bold; font-size: 12px;">&#10003; 워크플로우 완료</span>
<% if @issue.status_id != 5 && (User.current.admin? || current_step.assignee?(User.current)) %>
<%= link_to '완료 처리'.html_safe,
issue_workflow_complete_path(@issue),
method: :post,
class: 'button button-positive',
style: 'font-size: 11px; padding: 4px 10px; margin-left: 10px;',
data: { confirm: '이슈를 완료 처리하시겠습니까?' } %>
<% end %>
<% end %>
<% if !current_step.is_start && workflow_state.can_go_back?(User.current) %>
<%= link_to '&larr; 이전'.html_safe,
issue_workflow_prev_step_path(@issue),
method: :post,
class: 'button',
style: 'font-size: 11px; padding: 4px 10px; margin-left: 5px;',
data: { confirm: '이전 단계로 되돌리시겠습니까?' } %>
<% end %>
<% if !current_step.is_start && (User.current.admin? || current_step.assignee?(User.current)) %>
<a href="#" onclick="showRejectModal(); return false;"
class="button"
style="font-size: 11px; padding: 4px 10px; margin-left: 5px; background: #dc3545; color: white;">
반려
</a>
<% end %>
<% if User.current.admin? %>
<a href="#" onclick="showSkipModal(); return false;"
class="button"
style="font-size: 11px; padding: 4px 10px; margin-left: 5px; background: #6c757d; color: white;">
단계 이동
</a>
<% end %>
</div>
<% if current_step.due_days.present? %>
<%
step_started_at = workflow_state.updated_at
due_date = step_started_at + current_step.due_days.days
remaining = ((due_date - Time.current) / 1.day).to_i
%>
<div style="margin-top: 8px; font-size: 11px;">
<% if remaining < 0 %>
<span style="color: #dc3545; font-weight: bold;">
기한 초과 <%= remaining.abs %>일
</span>
<% elsif remaining == 0 %>
<span style="color: #f57c00; font-weight: bold;">
오늘 마감
</span>
<% else %>
<span style="color: #666;">
남은 기한: <%= remaining %>일 (<%= due_date.strftime('%Y-%m-%d') %>)
</span>
<% end %>
</div>
<% end %>
<% end %>
</div>
<!-- 반려 모달 -->
<div id="reject-modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000;">
<div style="background: white; width: 400px; margin: 100px auto; padding: 20px; border-radius: 8px;">
<h3 style="margin-top: 0; color: #dc3545;">반려</h3>
<p style="color: #666; font-size: 12px;">최초 단계(<%= steps.first&.name %>)로 반려됩니다.</p>
<%= form_tag issue_workflow_reject_path(@issue), method: :post do %>
<p>
<label><strong>반려 사유:</strong></label><br/>
<textarea name="reason" rows="4" style="width: 100%; padding: 5px;" placeholder="반려 사유를 입력하세요..." required></textarea>
</p>
<p style="text-align: right;">
<button type="button" onclick="closeRejectModal()" class="button">취소</button>
<button type="submit" class="button" style="background: #dc3545; color: white;">반려</button>
</p>
<% end %>
</div>
</div>
<!-- 다음 단계 담당자 선택 모달 -->
<% if next_step_has_group && next_step_group_members.count > 1 %>
<div id="next-step-modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000;">
<div style="background: white; width: 400px; margin: 100px auto; padding: 20px; border-radius: 8px;">
<h3 style="margin-top: 0; color: #007bff;">다음 단계: <%= next_step.name %></h3>
<p style="color: #666; font-size: 12px;">담당자를 선택하세요.</p>
<%= form_tag issue_workflow_next_step_path(@issue), method: :post do %>
<p>
<label><strong>담당자 선택:</strong></label><br/>
<% next_step_group_members.each do |member| %>
<label style="display: block; padding: 8px; margin: 5px 0; background: #f8f9fa; border-radius: 4px; cursor: pointer;">
<input type="radio" name="assignee_id" value="<%= member.id %>" required style="margin-right: 8px;">
<%= member.name %>
</label>
<% end %>
</p>
<p style="text-align: right;">
<button type="button" onclick="closeNextStepModal()" class="button">취소</button>
<button type="submit" class="button button-positive">다음 단계로</button>
</p>
<% end %>
</div>
</div>
<% end %>
<!-- 단계 이동 모달 (관리자용) -->
<% if User.current.admin? %>
<div id="skip-modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000;">
<div style="background: white; width: 400px; margin: 100px auto; padding: 20px; border-radius: 8px;">
<h3 style="margin-top: 0; color: #6c757d;">단계 이동 (관리자)</h3>
<p style="color: #666; font-size: 12px;">원하는 단계로 직접 이동합니다.</p>
<%= form_tag issue_workflow_skip_step_path(@issue), method: :post do %>
<p>
<label><strong>이동할 단계:</strong></label><br/>
<select name="target_step_id" style="width: 100%; padding: 5px;" required>
<option value="">-- 선택 --</option>
<% steps.each do |step| %>
<% next if current_step && step.id == current_step.id %>
<option value="<%= step.id %>">
<%= step.name %>
<% if step.is_start %>(시작)<% end %>
<% if step.is_end %>(종료)<% end %>
</option>
<% end %>
</select>
</p>
<p>
<label><strong>사유 (선택):</strong></label><br/>
<textarea name="reason" rows="3" style="width: 100%; padding: 5px;" placeholder="이동 사유를 입력하세요..."></textarea>
</p>
<p style="text-align: right;">
<button type="button" onclick="closeSkipModal()" class="button">취소</button>
<button type="submit" class="button" style="background: #6c757d; color: white;">이동</button>
</p>
<% end %>
</div>
</div>
<% end %>
<script>
function showRejectModal() {
document.getElementById('reject-modal').style.display = 'block';
}
function closeRejectModal() {
document.getElementById('reject-modal').style.display = 'none';
}
document.getElementById('reject-modal').addEventListener('click', function(e) {
if (e.target === this) closeRejectModal();
});
<% if next_step_has_group && next_step_group_members.count > 1 %>
function showNextStepModal() {
document.getElementById('next-step-modal').style.display = 'block';
}
function closeNextStepModal() {
document.getElementById('next-step-modal').style.display = 'none';
}
document.getElementById('next-step-modal').addEventListener('click', function(e) {
if (e.target === this) closeNextStepModal();
});
<% end %>
<% if User.current.admin? %>
function showSkipModal() {
document.getElementById('skip-modal').style.display = 'block';
}
function closeSkipModal() {
document.getElementById('skip-modal').style.display = 'none';
}
document.getElementById('skip-modal').addEventListener('click', function(e) {
if (e.target === this) closeSkipModal();
});
<% end %>
</script>
<% end %>