-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Size-M開発時間の目安は10時間開発時間の目安は10時間✨Backendバックエンドのタスク. 主にGo, TypeScriptを使用バックエンドのタスク. 主にGo, TypeScriptを使用✨Frontend-MobileMobileのフロントのタスク. 主にDart/Flutterを使用Mobileのフロントのタスク. 主にDart/Flutterを使用優先度1Better・なるべくここまでは実装したい枠Better・なるべくここまでは実装したい枠
Description
開発概要
目的
- 現在、アプリ内のメニューでGoogleドキュメントへのリンクとして実装されている「マニュアル表示」を、アプリ内で直接PDFとして閲覧できるUIに改善する
- トグル(ExpansionTileなど)を開いた際に、その中でPDFが表示される仕組みを構築する
- ユーザーがアプリを離れることなく、マニュアルを確認できるようにする
- バックエンドでGoogleドキュメントのURLをPDF形式に変換し、フロントエンドでPDF表示できるようにする
開発期間
- 開始日:
- 締切日:
考えられる開発内容
Phase 1: バックエンド実装(API側)
GoogleドキュメントURL変換処理の実装
-
api/lib/utils/google_docs_converter.goを作成(新規ファイル)- GoogleドキュメントのURLをPDF形式に変換する関数を実装
ConvertToPdfUrl(url string) stringメソッドを実装- 対応パターン:
- Google Docs:
https://docs.google.com/document/d/{DOC_ID}/edit→https://docs.google.com/document/d/{DOC_ID}/export?format=pdf - Google Sheets:
https://docs.google.com/spreadsheets/d/{SHEET_ID}/edit#gid={GID}→https://docs.google.com/spreadsheets/d/{SHEET_ID}/export?format=pdf&gid={GID}
- Google Docs:
- URLがGoogleドキュメントでない場合(既にPDF URLなど)はそのまま返す
Taskエンティティの拡張
-
api/lib/entity/task.goを更新Task構造体にPdfUrlフィールドを追加(JSONタグ:pdfUrl)- 既存の
UrlフィールドはGoogleドキュメントのURLとして保持(後方互換性のため)
TaskUseCaseの更新
-
api/lib/usecase/task_usecase.goを更新GetTasksメソッドで、各タスクのUrlをConvertToPdfUrlで変換し、PdfUrlフィールドに設定GetTaskByIDメソッドでも同様の処理を追加GetTasksByShiftメソッドでも同様の処理を追加GetTasksByUserIDメソッドでも同様の処理を追加- 既存の
Urlフィールドはそのまま保持(既存の機能への影響を避けるため)
動作確認
-
/tasksエンドポイントのレスポンスにpdfUrlフィールドが含まれているか確認 - GoogleドキュメントのURLが正しくPDF形式に変換されているか確認
- 既存の
urlフィールドがそのまま返却されているか確認(後方互換性)
Phase 2: フロントエンド実装(Mobile側)
PDF表示ライブラリの導入
-
mobile/pubspec.yamlにpdfx: ^1.3.0を追加 -
flutter pub getを実行してパッケージをインストール -
flutter pub run pdfx:install_webを実行してWeb対応のセットアップ(PDF.jsをweb/index.htmlに追加)
manual_list_page.dartのUI変更
ExpansionTileへの変更
-
mobile/lib/pages/manual_list_page.dartの_manualItemメソッドを修正ListTileをExpansionTileに変更- タイトルを「業務マニュアル」またはマニュアルのタスク名に設定
- 既存のレイアウト構造(
Container、padding、ListView.builder)を維持 - 高さの制約(
height: 40)を削除し、ExpansionTileの自然な高さに任せる
PDF表示Widgetの実装
-
_ManualItemStateクラスを作成(StatefulWidgetとして分離、または既存のStateクラス内で状態管理)- PDF読み込み状態を管理(
isLoading,hasError,pdfUrl)
- PDF読み込み状態を管理(
- ExpansionTileの
children内にPDF表示Widgetを実装PdfViewPinchまたはPdfViewを使用PdfControllerPinchでPDFドキュメントを制御- APIから取得した
pdfUrlフィールドを使用(manuals[index]["pdfUrl"]) pdfUrlが空の場合はurlフィールドを使用(フォールバック)
ローディングとエラーハンドリング
- PDF読み込み中のローディングインジケーターを実装
PdfViewPinchのloadingBuilderでCircularProgressIndicatorを表示
- PDF読み込み失敗時のエラーハンドリングを実装
PdfViewPinchのerrorBuilderでエラーメッセージを表示- CORS制限やネットワークエラーに対応
フォールバック機能の実装
- 「ブラウザで開く」ボタンを実装
ElevatedButtonまたはTextButtonを使用url_launcherのlaunchUrlで外部ブラウザを開く- 既存の
_launchManualUrlメソッドと同様の実装 - PDF表示エリアの下に配置
- エラー時も表示して代替手段を提供
urlフィールド(Googleドキュメントの元のURL)を使用
レイアウト調整
- ExpansionTileの
children内のレイアウトを調整SizedBoxでPDF表示エリアの高さを制限(例:height: 400)- PDF表示エリアの上下に適切なパディングを設定
- 「ブラウザで開く」ボタンとPDF表示エリアの間隔を調整
Phase 3: 動作確認
バックエンドのテスト
-
/tasksエンドポイントのレスポンス確認pdfUrlフィールドが正しく含まれているか- GoogleドキュメントのURLが正しくPDF形式に変換されているか
- 既存の
urlフィールドがそのまま返却されているか
フロントエンドのテスト
- モバイル(iOS/Android)での動作確認
- PDFの表示が正常に動作するか
- ローディングインジケーターが表示されるか
- エラーハンドリングが正常に動作するか
- 「ブラウザで開く」ボタンが正常に動作するか
- Webでの動作確認
web/index.htmlにPDF.jsが正しく追加されているか- PDFの表示が正常に動作するか
- CORS制限がないか確認
- 既存レイアウトの確認
- メニュー画面のレイアウトが崩れていないか
- 他の機能に影響がないか
- 統合テスト
- バックエンドとフロントエンドが正しく連携しているか
- APIから取得した
pdfUrlが正しく使用されているか
備考
- PDFライブラリの選定:
pdfxパッケージを採用(オープンソース、Web対応、商用ライセンス不要) - バックエンドとフロントエンドの連携: バックエンドでGoogleドキュメントのURLをPDF形式に変換し、フロントエンドでPDF表示する方式を採用
- 後方互換性: 既存の
urlフィールドはそのまま保持し、新しくpdfUrlフィールドを追加することで、既存の機能への影響を最小限に抑えます - GoogleドキュメントのURL変換:
- Google Docs:
/editを/export?format=pdfに変換 - Google Sheets:
/edit#gid={GID}を/export?format=pdf&gid={GID}に変換 - 既にPDF URLの場合はそのまま返す
- Google Docs:
- CORS制限: GoogleドキュメントのPDFエクスポートURLはCORS制限がある可能性があります。エラーハンドリングで対応し、「ブラウザで開く」ボタンで代替手段を提供します
- Webビルド: Webビルドでは
web/index.htmlにPDF.jsのスクリプトが追加されていることを確認してください - パフォーマンス: 大きなPDFファイルの読み込みには時間がかかる可能性があります。ローディングインジケーターでユーザーに状態を伝えます
- 既存機能への影響:
shift_card.dartの_buildManualSectionは現時点では変更しません。必要に応じて後で同様の改善を検討できます- 既存のAPIクライアント(Admin側など)への影響を確認し、必要に応じて対応します
参考
対象ファイル
バックエンド(API)
api/lib/utils/google_docs_converter.go- 新規作成(GoogleドキュメントURL変換処理)api/lib/entity/task.go-PdfUrlフィールドの追加api/lib/usecase/task_usecase.go- URL変換処理の追加api/lib/internals/controller/task_controller.go- 変更なし(既存のエンドポイントを使用)
フロントエンド(Mobile)
mobile/lib/pages/manual_list_page.dart- メインの変更対象mobile/pubspec.yaml- PDF表示ライブラリの追加mobile/lib/widgets/shift_card.dart- 参考(ExpansionTileの実装例)
実装の詳細
バックエンド: GoogleドキュメントURL変換処理の実装例
package utils
import (
"regexp"
"strings"
)
// ConvertToPdfUrl GoogleドキュメントのURLをPDF形式に変換
func ConvertToPdfUrl(url string) string {
if url == "" {
return ""
}
// Google Docs のパターン
// https://docs.google.com/document/d/{DOC_ID}/edit
docPattern := regexp.MustCompile(`https://docs\.google\.com/document/d/([a-zA-Z0-9_-]+)/.*`)
if matches := docPattern.FindStringSubmatch(url); len(matches) > 1 {
docID := matches[1]
return "https://docs.google.com/document/d/" + docID + "/export?format=pdf"
}
// Google Sheets のパターン
// https://docs.google.com/spreadsheets/d/{SHEET_ID}/edit#gid={GID}
sheetPattern := regexp.MustCompile(`https://docs\.google\.com/spreadsheets/d/([a-zA-Z0-9_-]+)/.*gid=([0-9]+)`)
if matches := sheetPattern.FindStringSubmatch(url); len(matches) > 2 {
sheetID := matches[1]
gid := matches[2]
return "https://docs.google.com/spreadsheets/d/" + sheetID + "/export?format=pdf&gid=" + gid
}
// 既にPDF URLまたはその他のURLの場合はそのまま返す
return url
}バックエンド: TaskUseCaseでの使用例
func (b *taskUseCase) GetTasks(c context.Context) ([]entity.Task, error) {
// ... 既存の処理 ...
for rows.Next() {
err := rows.Scan(
&task.ID,
&task.Task,
&task.PlaceID,
&task.Url,
// ... 他のフィールド ...
)
if err != nil {
return nil, errors.Wrapf(err, "cannot connect SQL")
}
// PDF URLを生成
task.PdfUrl = utils.ConvertToPdfUrl(task.Url)
tasks = append(tasks, task)
}
return tasks, nil
}フロントエンド: PDF表示の実装例
PdfViewPinch(
controller: PdfControllerPinch(
document: PdfDocument.openUrl(manuals[index]["pdfUrl"] ?? manuals[index]["url"]),
),
builders: PdfViewPinchBuilders<DefaultBuilderOptions>(
loadingBuilder: (context, progress) => Center(
child: CircularProgressIndicator(),
),
errorBuilder: (context, error) => Center(
child: Text('PDF読み込みエラー: ${error.toString()}'),
),
),
)フロントエンド: ExpansionTileの実装例
ExpansionTile(
title: Text(manuals[index]["task"].toString()),
children: [
SizedBox(
height: 400,
child: PdfViewPinch(...),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () => _launchManualUrl(manuals[index]["url"]),
child: Text('ブラウザで開く'),
),
),
],
)データフロー
マニュアル一覧ページ表示(Mobile)
↓
APIリクエスト: GET /tasks
↓
TaskUseCase.GetTasks() 実行
↓
データベースからタスク取得
↓
各タスクのUrlをConvertToPdfUrl()で変換
↓
PdfUrlフィールドを設定してレスポンス返却
↓
Mobile側でgetAllManual()がレスポンスを受信
↓
各マニュアルをExpansionTileで表示
↓
ユーザーがトグルを開く
↓
pdfUrlを使用してPDFを読み込み(PdfViewPinch)
↓
ローディング表示 → PDF表示
↓
(エラー時)エラーメッセージ + 「ブラウザで開く」ボタン(元のurlを使用)
参考リンク
バックエンド関連
フロントエンド関連
開発の流れ
- PMにIssue(タスク)をもらう
- 開発をする(↓の「リンク」の『開発のやり方』を見よう!)
- チェックボックスを押していこう
- ヤバい状況になったらIssueの右側にあるStatusを「Help」にしてPMにSlackで連絡しよう
- チェックボックスが全部押せたらプルリクを作ろう
- レビューを待とう
- 修正点があれば修正しよう。なければPMがマージします!お疲れ様!
SeeFTのタスク管理のルール
- タスクは全てGit-Hub Projectで管理する
- 全てのタスクに期日を決める
- 毎週タスクの進捗を確認する(MTに出られない人はSlackで報告)
- 毎週忙しさ(消化できるタスク量)を共有する
- Helpは余裕のある人がいれば巻き取る。いなければ期日を変更する
リンク
Metadata
Metadata
Labels
Size-M開発時間の目安は10時間開発時間の目安は10時間✨Backendバックエンドのタスク. 主にGo, TypeScriptを使用バックエンドのタスク. 主にGo, TypeScriptを使用✨Frontend-MobileMobileのフロントのタスク. 主にDart/Flutterを使用Mobileのフロントのタスク. 主にDart/Flutterを使用優先度1Better・なるべくここまでは実装したい枠Better・なるべくここまでは実装したい枠