プロジェクト

全般

プロフィール

バグ #148

未完了

【設計】Redmineカンバンビュー拡張機能

Redmine Admin さんが5日前に追加. 5日前に更新.

ステータス:
解決
優先度:
通常
担当者:
-
開始日:
2025-06-02
期日:
進捗率:

0%

予定工数:

説明

概要

Redmineカンバンビュー拡張機能の設計を行う。

設計項目

  1. アーキテクチャ設計

    • プラグイン構造
    • クラス設計
    • データフロー
  2. 詳細ボタン実装設計

    • HTML/CSS/JavaScript設計
    • イベントハンドリング設計
    • レスポンシブデザイン対応
  3. 担当者別カンバンビュー設計

    • コントローラー設計
    • モデル設計
    • ビュー設計
    • クエリ最適化設計
  4. 日次/週次/月次フィルター設計

    • UI設計
    • フィルタリングロジック設計
    • 日付計算アルゴリズム設計
  5. データベース設計

    • 既存テーブルへの影響分析
    • クエリパフォーマンス設計

成果物

  • 詳細設計書
  • クラス図
  • シーケンス図
  • データベース設計図(必要な場合)
  • API設計書(必要な場合)

参考情報

  • Redmineのバージョン: 最新版
  • 対象環境: task2.call2arm.com
  • 既存プラグイン: Kanban (Agile) board または redmine_agile

現状

設計前の初期段階。仕様検討の完了を待っている状態。

Redmine Admin さんが5日前に更新

親チケット #146 の子チケットとして設定します。

Redmine Admin さんが5日前に更新

【設計】Redmineカンバンビュー拡張機能

1. アーキテクチャ設計

1.1 全体構成

+-----------------------------------+
| Redmine API サーバー              |
| (call2arm.com)                    |
+-----------------------------------+
               ↑↓
+-----------------------------------+
| React カスタムUI                  |
| (task2.call2arm.com)              |
|                                   |
| +-------------------------------+ |
| | コンポーネント                | |
| |                               | |
| | - KanbanBoard                 | |
| | - KanbanCard                  | |
| | - AssigneeKanbanView          | |
| | - DateRangeFilter             | |
| +-------------------------------+ |
|                                   |
| +-------------------------------+ |
| | サービス                      | |
| |                               | |
| | - RedmineApiService           | |
| | - KanbanDataService           | |
| | - DateFilterService           | |
| +-------------------------------+ |
+-----------------------------------+

1.2 データフロー

User Action → React Component → API Service → Redmine API → 
API Response → State Update → Component Re-render

2. コンポーネント設計

2.1 コンポーネント階層

App
└── MainLayout
    └── TicketsPage
        ├── TicketsHeader
        │   ├── ViewToggle (List/Kanban/AssigneeKanban)
        │   └── DateRangeFilter
        │
        ├── TicketsList (既存)
        ├── KanbanBoard (新規)
        │   ├── KanbanColumn
        │   │   └── KanbanCard
        │   │       └── DetailButton
        │   └── DragDropContext
        │
        └── AssigneeKanbanBoard (新規)
            ├── AssigneeRow
            │   └── StatusCell
            │       └── KanbanCard
            │           └── DetailButton
            └── DateRangeContext

2.2 各コンポーネントの責務

KanbanBoard

  • 責務: 標準的なカンバンボードを表示(列がステータス)
  • Props:
    • tickets: チケット配列
    • statuses: ステータス配列
    • dateRange: 日付範囲
    • onTicketMove: チケット移動時のコールバック
  • 状態:
    • groupedTickets: ステータス別にグループ化されたチケット

AssigneeKanbanBoard

  • 責務: 担当者別カンバンボードを表示(行が担当者、列がステータス)
  • Props:
    • tickets: チケット配列
    • statuses: ステータス配列
    • users: ユーザー配列
    • dateRange: 日付範囲
    • onTicketMove: チケット移動時のコールバック
  • 状態:
    • matrixTickets: 担当者×ステータスのマトリックス形式のチケット

KanbanCard

  • 責務: チケット情報をカード形式で表示
  • Props:
    • ticket: チケット情報
    • isDraggable: ドラッグ可能かどうか
    • showDetailButton: 詳細ボタンを表示するかどうか
  • 機能:
    • チケット情報表示
    • ドラッグ&ドロップ対応
    • 詳細ボタンの表示

DetailButton

  • 責務: チケット詳細画面へのリンクボタン
  • Props:
    • ticketId: チケットID
    • style: ボタンスタイル
  • 機能:
    • クリック時にチケット詳細画面に遷移

DateRangeFilter

  • 責務: 日付範囲フィルターの表示と制御
  • Props:
    • dateRange: 現在の日付範囲
    • onDateRangeChange: 日付範囲変更時のコールバック
  • 状態:
    • filterType: 'daily', 'weekly', 'monthly', 'custom'
    • customStartDate: カスタム開始日
    • customEndDate: カスタム終了日

3. データモデル設計

3.1 基本データ構造

// チケット
interface Ticket {
  id: number;
  subject: string;
  status_id: number;
  assigned_to_id: number | null;
  start_date: string | null;
  due_date: string | null;
  project_id: number;
  priority_id: number;
  done_ratio: number;
  // その他の属性
}

// ステータス
interface Status {
  id: number;
  name: string;
  is_closed: boolean;
}

// ユーザー
interface User {
  id: number;
  name: string;
  avatar_url: string | null;
}

// 日付範囲
interface DateRange {
  startDate: Date | null;
  endDate: Date | null;
  type: 'daily' | 'weekly' | 'monthly' | 'custom';
}

3.2 状態管理設計

// アプリケーション状態
interface AppState {
  tickets: Ticket[];
  statuses: Status[];
  users: User[];
  projects: Project[];
  currentView: 'list' | 'kanban' | 'assigneeKanban';
  dateRange: DateRange;
  loading: boolean;
  error: string | null;
}

// アクション
enum ActionType {
  FETCH_TICKETS_REQUEST = 'FETCH_TICKETS_REQUEST',
  FETCH_TICKETS_SUCCESS = 'FETCH_TICKETS_SUCCESS',
  FETCH_TICKETS_FAILURE = 'FETCH_TICKETS_FAILURE',
  UPDATE_TICKET_STATUS = 'UPDATE_TICKET_STATUS',
  CHANGE_VIEW = 'CHANGE_VIEW',
  SET_DATE_RANGE = 'SET_DATE_RANGE',
  // その他のアクション
}

4. API設計

4.1 Redmine APIとの連携

// API サービス
class RedmineApiService {
  // チケット一覧取得
  async getTickets(params: {
    project_id?: number;
    status_id?: number;
    assigned_to_id?: number;
    start_date?: string;
    due_date?: string;
  }): Promise<Ticket[]> {
    // APIリクエスト実装
  }

  // チケット更新
  async updateTicket(id: number, data: {
    status_id?: number;
    assigned_to_id?: number;
    // その他の更新可能フィールド
  }): Promise<Ticket> {
    // APIリクエスト実装
  }

  // ステータス一覧取得
  async getStatuses(): Promise<Status[]> {
    // APIリクエスト実装
  }

  // ユーザー一覧取得
  async getUsers(): Promise<User[]> {
    // APIリクエスト実装
  }
}

4.2 日付フィルター処理

// 日付フィルターサービス
class DateFilterService {
  // 日次フィルター
  static getDaily(): DateRange {
    const today = new Date();
    return {
      startDate: today,
      endDate: today,
      type: 'daily'
    };
  }

  // 週次フィルター
  static getWeekly(): DateRange {
    const today = new Date();
    const startOfWeek = new Date(today);
    startOfWeek.setDate(today.getDate() - today.getDay());
    const endOfWeek = new Date(startOfWeek);
    endOfWeek.setDate(startOfWeek.getDate() + 6);
    
    return {
      startDate: startOfWeek,
      endDate: endOfWeek,
      type: 'weekly'
    };
  }

  // 月次フィルター
  static getMonthly(): DateRange {
    const today = new Date();
    const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
    const endOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0);
    
    return {
      startDate: startOfMonth,
      endDate: endOfMonth,
      type: 'monthly'
    };
  }

  // チケットのフィルタリング
  static filterTicketsByDateRange(tickets: Ticket[], dateRange: DateRange): Ticket[] {
    if (!dateRange.startDate || !dateRange.endDate) {
      return tickets;
    }

    return tickets.filter(ticket => {
      const ticketStartDate = ticket.start_date ? new Date(ticket.start_date) : null;
      const ticketDueDate = ticket.due_date ? new Date(ticket.due_date) : null;

      // 開始日も終了日も未設定のチケットは常に表示
      if (!ticketStartDate && !ticketDueDate) {
        return true;
      }

      // チケット開始日 <= フィルター終了日 かつ 
      // (チケット終了日がない または チケット終了日 >= フィルター開始日)
      return (!ticketStartDate || ticketStartDate <= dateRange.endDate) && 
             (!ticketDueDate || ticketDueDate >= dateRange.startDate);
    });
  }
}

5. UI設計

5.1 チケットパネル詳細ボタン

// DetailButton コンポーネント
const DetailButton: React.FC<{ ticketId: number }> = ({ ticketId }) => {
  return (
    <Link 
      to={`/redmine-ui/tickets/${ticketId}`} 
      className="detail-button"
      title="チケット詳細を表示"
    >
      詳細
    </Link>
  );
};

// スタイル (CSS-in-JS または CSS)
const styles = {
  detailButton: {
    position: 'absolute',
    bottom: '8px',
    right: '8px',
    padding: '2px 6px',
    backgroundColor: '#f0f0f0',
    border: '1px solid #ccc',
    borderRadius: '3px',
    fontSize: '0.8em',
    textDecoration: 'none',
    color: '#333',
    '&:hover': {
      backgroundColor: '#e0e0e0',
    }
  }
};

5.2 担当者別カンバンビュー

// AssigneeKanbanBoard コンポーネント(部分抜粋)
const AssigneeKanbanBoard: React.FC<AssigneeKanbanBoardProps> = ({
  tickets,
  statuses,
  users,
  dateRange,
}) => {
  // チケットをマトリックス形式に変換
  const ticketMatrix = useMemo(() => {
    // マトリックス初期化ロジック
    // ...
    
    return matrix;
  }, [tickets, statuses, users, dateRange]);
  
  return (
    <div className="assignee-kanban-board">
      <table>
        <thead>
          <tr>
            <th>担当者</th>
            {statuses.map(status => (
              <th key={status.id}>{status.name}</th>
            ))}
          </tr>
        </thead>
        <tbody>
          {/* ユーザー行の出力 */}
          {/* ... */}
        </tbody>
      </table>
    </div>
  );
};

5.3 日次/週次/月次フィルター

// DateRangeFilter コンポーネント(部分抜粋)
const DateRangeFilter: React.FC<DateRangeFilterProps> = ({
  dateRange,
  onDateRangeChange,
}) => {
  // 状態とイベントハンドラ
  // ...
  
  return (
    <div className="date-range-filter">
      <div className="filter-buttons">
        <button
          className={filterType === 'daily' ? 'active' : ''}
          onClick={() => handleFilterTypeChange('daily')}
        >
          日次
        </button>
        {/* 他のボタン */}
      </div>
      
      {/* カスタム日付範囲入力フォーム */}
    </div>
  );
};

6. 実装計画

6.1 フェーズ分け

  1. フェーズ1: 基本カンバンビュー

    • 標準カンバンボード実装
    • チケットカード実装
    • 詳細ボタン実装
  2. フェーズ2: 担当者別カンバンビュー

    • 担当者×ステータスマトリックス実装
    • API連携実装
  3. フェーズ3: 日付フィルター

    • 日次/週次/月次フィルター実装
    • カスタム日付範囲実装
  4. フェーズ4: 最適化

    • パフォーマンス改善
    • UI/UX改善
    • バグ修正

6.2 開発期間見積もり

  • フェーズ1: 3日
  • フェーズ2: 4日
  • フェーズ3: 2日
  • フェーズ4: 1日
  • 合計: 10日

7. まとめ

本設計書では、Redmineカンバンビュー拡張機能の詳細な設計を行いました。仕様検討フェーズでの要件に基づき、コンポーネント設計、データフロー設計、API設計、UI設計の詳細を定義しました。

この設計に基づいて実装フェーズに進むことで、以下の機能を備えたカンバンビュー拡張機能を実現します:

  1. チケットパネルに詳細ボタンを追加
  2. 担当者別カンバンビューの実装
  3. 日次/週次/月次でのフィルタリング機能
  4. 日付範囲に基づくチケット表示

Redmine Admin さんが5日前に更新

  • ステータス新規 から 解決 に変更

設計完了。実装フェーズに移行します。

他の形式にエクスポート: Atom PDF