AIと生きる~Cursorでアプリ開発 #12~
開発の続き
前回、新規登録ページを追加して、知識が増えたので他のページも修正してみると書きました。
結果、大幅に改良しました。
当初、「こういう挙動になってほしいな~」と考えていたことに近づけました。
indexページ
「Bootstrap」
ナビゲーションバー ⇒ そのまま
「Card」 ⇒ 「Accordion-flush」
顧客管理ページ
新規登録 ⇒ 新規登録ページからのみ
編集・更新のみ実行する仕様に変更
FHメンテナンス管理ページ
今アプリのメインとなるページになります。
顧客情報とメンテナンス情報を同時に新規登録する場合 ⇒ 新規登録ページから実行
既存顧客に新規メンテナンス情報を登録する場合 ⇒ 【新規登録】ボタンをクリック
登録されているメンテナンス情報に変更があった場合 ⇒ 【既存メンテナンス情報の更新】ボタンをクリック
FHメンテナンス管理/新規登録
FHメンテナンス管理/既存メンテナンス情報の更新
従業員管理ページ
従業員の新規登録
登録されている従業員一覧から、削除する従業員を選択して削除する
今回のまとめ
pythonとhtmlに関しては、エラーがあっても、自力で修正できる
Script部分で、勝手にハマったと勘違いしていた
JavaScriptの知識が浅いまま開発を行ったので、「Cursor」が出力したコードの確認に手間取った
「Fkask」公式ドキュメントを一読したあとは、エラーや処理実行への対処が早くなった
現在もJavaScriptを理解しているわけではありませんが、それでも、生成AIにサポートしてもらいながら、ここまで修正することができました。
今回のアプリ開発で、今後の開発にいい経験を積むことができました。
「Cursor」でのアプリ開発は、これで完了しました。
今回の開発は、色々な気付きと自身の成長を感じられる、有意義な時間でした。
手こずったscriptコード
メインページとなる、メンテナンス管理ページのscript部分は下記のように作成しました。
他のページは、このページから流用しているので、今回作成したJavaScriptコードがすべて含まれています。
これも、CursorのAI機能に指示(プロンプト)を伝えて作成してもらいました。
<script>
// 新規登録ボタンがクリックされたときの処理
$('#newRegistrationBtn').on('click', function() {
// 顧客検索ボックス表示
$('#searchCustomerBox').show();
// newRegistrationBtnとupdateMaintenanceBtnを消す
$('#newRegistrationBtn').hide();
$('#updateMaintenanceBtn').hide();
// 顧客検索ボタンがクリックされたときの処理
$('#searchCustomerBtn').on('click', function() {
// maintenanceTableテーブルを非表示にする
$('#maintenanceTable').hide();
var customerName = $('#customerName').val();
// 顧客名で顧客を検索するためのAJAX呼び出し
$.ajax({
url: '/search_customer',
type: 'POST', // FlaskエンドポイントがPOSTを受け付けるため
dataType: 'json', // JSON形式の応答を期待
data: { 'name': customerName }, // フォームデータとして顧客名を送信
success: function(response) {
if (response.error) {
alert('該当するお客様はおられません。新規登録ページから新規登録してください');
window.location.href = "registration";
} else {
// テーブルの内容をクリア
$('#searchResultsTable tbody').empty();
$('#searchResultsTable').show();
// JSONデータをテーブルに変換して表示
$.each(response.customers, function(index, customer) {
$('<tr>').data('id', customer.id).append( // 顧客IDをdata-id属性として追加
$('<td>').text(customer.id),
$('<td>').text(customer.name),
$('<td>').text(customer.address),
$('<td>').text(customer.telephone),
$('<td>').text(customer.usage_number),
// 他に表示したい列があればここに追加
).appendTo('#searchResultsTable tbody');
});
}
}
});
});
// 顧客検索結果テーブルの行をクリックしたときのイベント
$('#searchResultsTable').on('click', 'tbody tr', function() {
// 顧客検索結果テーブルを非表示にする
$('#searchResultsTable').hide();
// 検索ボックスを非表示にする
$('#searchCustomerBox').hide();
// customerFormを表示する
$('#customerForm').show();
$('#customer_id4').show();
$('#customer_id5').hide();
// maintenanceFormを表示する
$('#maintenanceForm').show();
// newbtnを表示する
$('#new').show();
// 選択された行の全カラムを取得して表示する
var customerValues = $(this).children('td').map(function() {
return $(this).text();
}).get();
// 顧客フォームの該当箇所に表示する
$('#customer_id2').prop('readonly', true).val(customerValues[0]);
$('#customer_name').prop('readonly', true).val(customerValues[1]);
$('#customer_address').prop('readonly', true).val(customerValues[2]);
$('#customer_telephone').prop('readonly', true).val(customerValues[3]);
$('#customer_usage_number').prop('readonly', true).val(customerValues[4]);
// メンテナンスフォームの該当箇所に表示する
$('#customer_id').prop('readonly', true).val(customerValues[0]);
});
});
// 入力フォームに入力中にエンターキーが押された場合、入力情報を保持してそのままページに留まる
$(document).on('keypress', 'input', function(e) {
if (e.keyCode == 13) {
e.preventDefault();
return false;
}
});
// 預り日数が変更されたときに、受付日に預り日数を加算して返却予定日を自動計算し、表示する
$('#number_of_days_deposited').on('change', function() {
var dateOfReceipt = new Date($('#date_of_receipt').val());
var numberOfDaysDeposited = parseInt($('#number_of_days_deposited').val());
if (!isNaN(dateOfReceipt.getTime()) && !isNaN(numberOfDaysDeposited)) {
var returnDate = new Date(dateOfReceipt.getTime() + numberOfDaysDeposited * 24 * 60 * 60 * 1000);
var formattedReturnDate = returnDate.getFullYear() + '-' + (returnDate.getMonth() + 1).toString().padStart(2, '0') + '-' + returnDate.getDate().toString().padStart(2, '0');
$('#scheduled_return_date').val(formattedReturnDate);
}
});
// 更新ボタンがクリックされたときの処理
$('#updateMaintenanceBtn').on('click', function() {
// 新規顧客とメンテナンス登録用のフォームを表示
$('#searchCustomerBox').show();
$('#maintenanceTable').show();
// newRegistrationBtnとupdateMaintenanceBtnを消す
$('#newRegistrationBtn').hide();
$('#updateMaintenanceBtn').hide();
// 顧客検索ボタンがクリックされたときの処理
$('#searchCustomerBtn').on('click', function() {
// 顧客検索結果テーブルを非表示にする
$('#searchResultsTable').hide();
// 検索ボックスを非表示にする
$('#searchCustomerBox').hide();
$('#maintenanceTable').hide();
var customerName = $('#customerName').val();
// 顧客名で顧客を検索するためのAJAX呼び出し
$.ajax({
url: '/search_customer',
type: 'POST', // FlaskエンドポイントがPOSTを受け付けるため
dataType: 'json', // JSON形式の応答を期待
data: { 'name': customerName }, // フォームデータとして顧客名を送信
success: function(response) {
if (response.error) {
$('#error-message').text('該当するお客様はおられません。顧客管理画面から新規登録してください');
$('#error-message').show();
} else {
// テーブルの内容をクリア
$('#searchResultsTable tbody').empty();
$('#searchResultsTable').show();
// JSONデータをテーブルに変換して表示
$.each(response.customers, function(index, customer) {
$('<tr>').data('id', customer.id).append( // 顧客IDをdata-id属性として追加
$('<td>').text(customer.id),
$('<td>').text(customer.name),
$('<td>').text(customer.address),
$('<td>').text(customer.telephone),
$('<td>').text(customer.usage_number),
// 他に表示したい列があればここに追加
).appendTo('#searchResultsTable tbody');
});
}
}
});
});
// 顧客検索結果テーブルの行をクリックしたときのイベント
$('#searchResultsTable').on('click', 'tbody tr', function() {
// テーブルを非表示にする
$('#searchResultsTable').hide();
// 検索ボックスを非表示にする
$('#searchCustomerBox').hide();
// customerFormを表示する
$('#customerForm').show();
$('#customer_id5').show();
// 選択された行の全カラムを取得して表示する
var customerValues = $(this).children('td').map(function() {
return $(this).text();
}).get();
// 顧客フォームの該当箇所に表示する
$('#customer_id3').prop('readonly', true).val(customerValues[0]);
$('#customer_name').prop('readonly', true).val(customerValues[1]);
$('#customer_address').prop('readonly', true).val(customerValues[2]);
$('#customer_telephone').prop('readonly', true).val(customerValues[3]);
$('#customer_usage_number').prop('readonly', true).val(customerValues[4]);
// 顧客IDに紐づくメンテナンス情報を取得してテーブルに一覧表示する処理を追加する
var customerId2 = customerValues[0];
$.ajax({
url: '/search_maintenance',
type: 'POST',
data: { 'customer_id3': customerId2 },
success: function(response) {
if (response.error) {
$('#error-message2').text('該当するメンテナンス情報はありません。メンテナンス情報を新規登録してください');
$('#error-message2').show();
} else {
// テーブルの内容をクリア
$('#searchResultsTable2 tbody').empty();
$('#searchResultsTable2').show();
// JSONデータをテーブルに変換して表示
$.each(response.maintenances, function(index, maintenance) {
$('<tr>').data('id', maintenance.id).append( // メンテナンスIDをdata-id属性として追加
$('<td>').text(maintenance.id),
$('<td>').text(maintenance.customer_id),
$('<td>').text(new Date(maintenance.date_of_receipt).toISOString().split('T')[0]),
$('<td>').text(maintenance.recipient_id),
$('<td>').text(maintenance.model_number),
$('<td>').text(maintenance.maintenance_person_id),
$('<td>').text(maintenance.paid_free),
$('<td>').text(maintenance.number_of_days_deposited),
$('<td>').text(new Date(maintenance.scheduled_return_date).toISOString().split('T')[0]),
$('<td>').text(maintenance.return_processing_date ? new Date(maintenance.return_processing_date).toISOString().split('T')[0] : ''),
$('<td>').text(maintenance.returned_unreturned),
$('<td>').text(maintenance.remarks),
// 他に表示したい列があればここに追加
).appendTo('#searchResultsTable2 tbody');
});
}
}
});
});
// メンテナンス検索結果テーブルの行をクリックしたときのイベント
$('#searchResultsTable2').on('click', 'tbody tr', function() {
// テーブルを非表示にする
$('#searchResultsTable2').hide();
// 検索ボックスを非表示にする
$('#searchCustomerBox').hide();
// customerFormを表示する
$('#maintenanceForm').show();
$('#maintenance_id2').show();
$('#up').show();
// 選択された行の全カラムを取得して表示する
var maintenanceValues = $(this).children('td').map(function() {
return $(this).text();
}).get();
console.log(maintenanceValues);
// メンテナンスフォームの該当箇所に表示する
$('#maintenance_id').prop('readonly', true).val(maintenanceValues[0]);
$('#customer_id').prop('readonly', true).val(maintenanceValues[1]);
$('#date_of_receipt').prop('readonly', true).val(maintenanceValues[2]);
$('#recipient_id').val(maintenanceValues[3]);
$('#model_number').val(maintenanceValues[4]);
$('#maintenance_person_id').val(maintenanceValues[5]);
if (maintenanceValues[6] == '有償') {
$('#paid_free1').prop('checked', true);
} else if (maintenanceValues[6] == '無償') {
$('#paid_free2').prop('checked', true);
}
$('#number_of_days_deposited').val(maintenanceValues[7]);
$('#scheduled_return_date').val(maintenanceValues[8]);
$('#return_processing_date').val(maintenanceValues[9]);
if (maintenanceValues[10] == '返却済') {
$('#returned_unreturned1').prop('checked', true);
} else if (maintenanceValues[10] == '未返却') {
$('#returned_unreturned2').prop('checked', true);
}
});
});
$('#new').click(function() {
document.getElementById('maintenanceForm').addEventListener('submit', function(event) {
event.preventDefault();
let maintenancedata = new FormData(this);
fetch('{{ url_for("register_maintenance") }}', {
method: 'POST',
body: maintenancedata,
})
.then(response => response.json())
.then(maintenancedata => {
switch(maintenancedata.message) {
case '登録が完了しました':
$('#maintenanceForm').hide();
alert('メンテナンス情報の新規登録が完了しました。');
window.location.href = "/";
break;
case 'データベースエラーが発生しました。登録に失敗しました。':
alert('データベースエラーが発生しました。メンテナンス管理ページからメンテナンス情報を登録してください');
window.location.href = "maintenance_management";
break;
case 'フォームのバリデーションに失敗しました':
alert('入力内容に誤りがあります。メンテナンス管理ページからメンテナンス情報を登録してください');
window.location.href = "maintenance_management";
break;
default:
alert('未知のメッセージ: ' + data.message);
}
});
});
});
$('#up').click(function() {
document.getElementById('maintenanceForm').addEventListener('submit', function(event) {
event.preventDefault();
let maintenancedata = new FormData(this);
fetch('{{ url_for("update_maintenance") }}', {
method: 'POST',
body: maintenancedata,
})
.then(response => response.json())
.then(maintenancedata => {
switch(maintenancedata.message) {
case '登録が完了しました':
$('#maintenanceForm2').hide();
alert('メンテナンス情報の更新が完了しました。');
window.location.href = "/";
break;
case 'データベースエラーが発生しました。登録に失敗しました。':
alert('データベースエラーが発生しました。メンテナンス管理ページからメンテナンス情報を登録しなおしてください');
window.location.href = "maintenance_management";
break;
case 'フォームのバリデーションに失敗しました':
alert('入力内容に誤りがあります。メンテナンス管理ページからメンテナンス情報を登録しなおしてください');
window.location.href = "maintenance_management";
break;
default:
alert('未知のメッセージ: ' + data.message);
}
});
});
});
// 全メンテナンス情報テーブルの行をクリックしたときのイベント
$('#maintenanceTable').on('click', 'tbody tr', function() {
// テーブルを非表示にする
$('#maintenanceTable').hide();
// 検索ボックスを非表示にする
$('#searchCustomerBox').hide();
// customerFormを表示する
$('#customerForm').show();
$('#customer_id4').show();
// maintenanceForm2を表示する
$('#maintenanceForm').show();
$('#up').show();
// 選択された行の全カラムを取得して表示する
var maintenanceValues = $(this).children('td').map(function() {
return $(this).text();
}).get();
console.log(maintenanceValues);
// 顧客フォームの該当箇所に表示する
$('#customer_id2').prop('readonly', true).val(maintenanceValues[1]);
$('#customer_name').prop('readonly', true).val(maintenanceValues[2]);
$('#customer_address').prop('readonly', true).val(maintenanceValues[3]);
$('#customer_telephone').prop('readonly', true).val(maintenanceValues[4]);
$('#customer_usage_number').prop('readonly', true).val(maintenanceValues[5]);
// メンテナンスフォームの該当箇所に表示する
$('#maintenance_id').prop('readonly', true).val(maintenanceValues[0]);
$('#customer_id').prop('readonly', true).val(maintenanceValues[1]);
$('#date_of_receipt').prop('readonly', true).val(maintenanceValues[6]);
$('#recipient_id').val(maintenanceValues[7]);
$('#model_number').val(maintenanceValues[9]);
$('#maintenance_person_id').val(maintenanceValues[10]);
if (maintenanceValues[12] == '有償') {
$('#paid_free1').prop('checked', true);
} else if (maintenanceValues[12] == '無償') {
$('#paid_free2').prop('checked', true);
}
$('#number_of_days_deposited').val(maintenanceValues[13]);
$('#scheduled_return_date').val(maintenanceValues[14]);
$('#return_processing_date').val(maintenanceValues[15]);
if (maintenanceValues[16] == '返却済') {
$('#returned_unreturned1').prop('checked', true);
} else if (maintenanceValues[16] == '未返却') {
$('#returned_unreturned2').prop('checked', true);
}
});
</script>
今の体調
少しマシにはなってきたものの、気候変動に影響されています。
作業を始めると、課題の過集中になってしまい、終了後の疲労感が大きくなってしまいます。