AIに簡単なアプリを作らせてみた

AIに簡単なアプリを作らせてみた

目次

はじめに
要件
実装
まとめ

はじめに

先日、AIサービスの一つである「Claude」が社内で導入されました。
「Claude(クロード)」とは?となる方もいるかもしれないので簡単に説明すると
生成AIの一つで一番有名なところでいうとChatGPTと同じような機能を持った対話型のAIサービスです。
今回はClaudeを使って簡単なアプリを作ってみた様子をお伝えしたいと思います。

要件

現在オフィスでは通常より少し大きいサイズのモニターが1台あります。
今回はそのモニターを予約するアプリを作成してみます。
まずは要件ですが簡単に下記のように設定しました。
・モニターは1台
・予約は日毎に予約
・1週間先までの予約状況が分かる画面も欲しい
こちらの要件でアプリを作りたいとClaudeに投げたところ…

このような画面が出来ました…
もはやこれで十分なのでは…と思うほどなかなかの出来でした。
しかし、実際に入力してみたところ、日にちは問題なく設定出来ましたが予約者名と使用目的が1文字しか入らない…
キャンセルボタンも反応しない…
これでは意味がないので修正の指示を出してみます

何度か修正を指示しましたが結果は変わらず…
どうやらアーティファクト画面(Claudeの機能でコードが表示されるだけでなく実際に実行した画面が表示されること)では入力等の操作がうまく動かない場合があるそうです…

実装

先ほどClaude内ではうまく動作しなかったので実際にコードを書き出してみて動作するか検証します。
いざ実装してみよう!と思ったのですがどうやらReact(リアクト)と呼ばれるJavaScriptで構築するように書かれているらしいです。
Reactは初めて触るのでClaudeにセットアップ方法を聞きながら試すも…上手くいかずセットアップすら完了しませんでした…
そこでまだ私が触ったことのあるVisual Studioで構築しようと思い、早速Visual Studioで構築出来るようにコードの書き換えを指示しました。
するとすぐにコードの書き直しが行われました。
ただアーティファクト画面では対応していないのか実行エラーとなり、プレビューは見れませんでした。
下記の画像の通りVisual Studioでの作成方法を聞くと丁寧に手順を提示してくれます。

<code>&lt;!DOCTYPE html>
&lt;html lang="ja">
&lt;head>
    &lt;meta charset="UTF-8">
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0">
    &lt;title>モニター予約システム&lt;/title>
    &lt;style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: linear-gradient(135deg, #e3f2fd 0%, #e8eaf6 100%);
            min-height: 100vh;
            padding: 20px;
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
        }

        .header {
            text-align: center;
            margin-bottom: 40px;
        }

        .header h1 {
            font-size: 2.5rem;
            color: #1976d2;
            margin-bottom: 10px;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 15px;
        }

        .header p {
            color: #666;
            font-size: 1.1rem;
        }

        .card {
            background: white;
            border-radius: 12px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            padding: 30px;
            margin-bottom: 20px;
        }

        .card-header {
            display: flex;
            justify-content: between;
            align-items: center;
            margin-bottom: 30px;
        }

        .card-title {
            font-size: 1.5rem;
            font-weight: bold;
            color: #333;
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .btn {
            padding: 10px 20px;
            border: none;
            border-radius: 8px;
            font-size: 1rem;
            cursor: pointer;
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .btn-primary {
            background: #1976d2;
            color: white;
        }

        .btn-primary:hover {
            background: #1565c0;
        }

        .btn-secondary {
            background: #6c757d;
            color: white;
        }

        .btn-secondary:hover {
            background: #5a6268;
        }

        .btn-danger {
            background: transparent;
            color: #dc3545;
            border: 1px solid #dc3545;
            font-size: 0.9rem;
            padding: 6px 12px;
        }

        .btn-danger:hover {
            background: #dc3545;
            color: white;
        }

        .btn-reset {
            background: #e9ecef;
            color: #495057;
        }

        .btn-reset:hover {
            background: #dee2e6;
        }

        .day-card {
            padding: 20px;
            border-radius: 8px;
            border: 2px solid;
            margin-bottom: 15px;
        }

        .day-card.available {
            background: #f1f8e9;
            border-color: #4caf50;
        }

        .day-card.reserved {
            background: #ffebee;
            border-color: #f44336;
        }

        .day-card.expired {
            background: #f5f5f5;
            border-color: #9e9e9e;
        }

        .day-card.today {
            box-shadow: 0 0 0 2px #2196f3;
        }

        .day-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 10px;
        }

        .day-info {
            display: flex;
            align-items: center;
            gap: 15px;
        }

        .day-date {
            font-size: 1.2rem;
            font-weight: bold;
            color: #333;
        }

        .day-status {
            display: flex;
            align-items: center;
            gap: 8px;
            font-weight: 500;
        }

        .day-status.available {
            color: #4caf50;
        }

        .day-status.reserved {
            color: #f44336;
        }

        .day-status.expired {
            color: #757575;
        }

        .reservation-details {
            margin-top: 15px;
            padding-left: 30px;
        }

        .reservation-item {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-bottom: 8px;
            font-size: 0.9rem;
            color: #666;
        }

        .form-group {
            margin-bottom: 20px;
        }

        .form-label {
            display: block;
            margin-bottom: 8px;
            font-weight: 500;
            color: #333;
        }

        .form-input {
            width: 100%;
            padding: 12px;
            border: 2px solid #e0e0e0;
            border-radius: 8px;
            font-size: 1rem;
            transition: border-color 0.3s ease;
        }

        .form-input:focus {
            outline: none;
            border-color: #1976d2;
        }

        .form-textarea {
            height: 80px;
            resize: vertical;
        }

        .form-buttons {
            display: flex;
            gap: 15px;
            margin-top: 30px;
        }

        .form-buttons .btn {
            flex: 1;
        }

        .required {
            color: #f44336;
        }

        .form-help {
            font-size: 0.85rem;
            color: #666;
            margin-top: 5px;
        }

        .notification {
            padding: 15px;
            border-radius: 8px;
            margin-bottom: 20px;
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .notification.success {
            background: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }

        .notification.error {
            background: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }

        .hidden {
            display: none;
        }

        .icon {
            width: 20px;
            height: 20px;
            display: inline-block;
        }

        .icon-large {
            width: 40px;
            height: 40px;
        }

        .footer {
            text-align: center;
            margin-top: 40px;
            color: #666;
            font-size: 0.9rem;
        }

        .navigation {
            display: flex;
            justify-content: center;
            gap: 20px;
            margin-bottom: 20px;
        }

        @media (max-width: 768px) {
            .header h1 {
                font-size: 2rem;
            }
            
            .card {
                padding: 20px;
            }
            
            .day-header {
                flex-direction: column;
                align-items: flex-start;
                gap: 10px;
            }
            
            .form-buttons {
                flex-direction: column;
            }
        }
    &lt;/style>
&lt;/head>
&lt;body>
    &lt;div class="container">
        &lt;!-- ヘッダー -->
        &lt;div class="header">
            &lt;h1>
                &lt;span class="icon-large">🖥️&lt;/span>
                モニター予約システム
            &lt;/h1>
            &lt;p>1台のモニターの予約管理システム&lt;/p>
        &lt;/div>

        &lt;!-- 通知メッセージ -->
        &lt;div id="notification" class="notification hidden">
            &lt;span id="notification-icon">&lt;/span>
            &lt;span id="notification-message">&lt;/span>
        &lt;/div>

        &lt;!-- メインコンテンツ -->
        &lt;div id="calendar-view" class="card">
            &lt;div class="card-header">
                &lt;h2 class="card-title">
                    &lt;span class="icon">📅&lt;/span>
                    予約状況カレンダー
                &lt;/h2>
                &lt;button class="btn btn-primary" onclick="showReservationForm()">
                    &lt;span class="icon">🖥️&lt;/span>
                    新規予約
                &lt;/button>
            &lt;/div>
            &lt;div id="calendar-content">
                &lt;!-- カレンダー内容はJavaScriptで動的に生成 -->
            &lt;/div>
        &lt;/div>

        &lt;div id="reservation-view" class="card hidden">
            &lt;div class="card-header">
                &lt;h2 class="card-title">
                    &lt;span class="icon">🖥️&lt;/span>
                    モニター予約
                &lt;/h2>
                &lt;button class="btn btn-secondary" onclick="showCalendar()">
                    &lt;span class="icon">📅&lt;/span>
                    カレンダーに戻る
                &lt;/button>
            &lt;/div>
            
            &lt;div class="form-group">
                &lt;label class="form-label">予約日 &lt;span class="required">*&lt;/span>&lt;/label>
                &lt;input type="date" id="selectedDate" class="form-input">
                &lt;div class="form-help">今日から1週間先まで予約可能です&lt;/div>
            &lt;/div>

            &lt;div class="form-group">
                &lt;label class="form-label">予約者名 &lt;span class="required">*&lt;/span>&lt;/label>
                &lt;input type="text" id="userName" class="form-input" placeholder="予約者名を入力してください">
            &lt;/div>

            &lt;div class="form-group">
                &lt;label class="form-label">使用目的&lt;/label>
                &lt;textarea id="purpose" class="form-input form-textarea" placeholder="モニターの使用目的を入力してください(任意)">&lt;/textarea>
            &lt;/div>

            &lt;div class="form-buttons">
                &lt;button class="btn btn-primary" onclick="addReservation()">予約する&lt;/button>
                &lt;button class="btn btn-reset" onclick="resetForm()">リセット&lt;/button>
            &lt;/div>
        &lt;/div>

        &lt;!-- フッター -->
        &lt;div class="footer">
            &lt;p>© 2025 モニター予約システム - 効率的な機器管理をサポート&lt;/p>
        &lt;/div>
    &lt;/div>

    &lt;script>
        // グローバル変数
        let reservations = {};
        let currentView = 'calendar';

        // 日付関連のヘルパー関数
        function formatDate(date) {
            const year = date.getFullYear();
            const month = String(date.getMonth() + 1).padStart(2, '0');
            const day = String(date.getDate()).padStart(2, '0');
            return `${year}-${month}-${day}`;
        }

        function formatDisplayDate(dateString) {
            const date = new Date(dateString + 'T00:00:00');
            const weekdays = &#91;'日', '月', '火', '水', '木', '金', '土'];
            const month = date.getMonth() + 1;
            const day = date.getDate();
            const weekday = weekdays&#91;date.getDay()];
            return `${month}/${day} (${weekday})`;
        }

        function getWeekDates() {
            const today = new Date();
            const dates = &#91;];
            for (let i = 0; i &lt; 7; i++) {
                const date = new Date(today);
                date.setDate(today.getDate() + i);
                dates.push(formatDate(date));
            }
            return dates;
        }

        // 通知システム
        function showNotification(message, type) {
            const notification = document.getElementById('notification');
            const messageSpan = document.getElementById('notification-message');
            const iconSpan = document.getElementById('notification-icon');
            
            messageSpan.textContent = message;
            iconSpan.textContent = type === 'success' ? '✅' : '❌';
            
            notification.className = `notification ${type}`;
            notification.classList.remove('hidden');
            
            setTimeout(() => {
                notification.classList.add('hidden');
            }, 3000);
        }

        // ビュー切り替え
        function showCalendar() {
            document.getElementById('calendar-view').classList.remove('hidden');
            document.getElementById('reservation-view').classList.add('hidden');
            currentView = 'calendar';
            renderCalendar();
        }

        function showReservationForm() {
            document.getElementById('calendar-view').classList.add('hidden');
            document.getElementById('reservation-view').classList.remove('hidden');
            currentView = 'reservation';
            setupDateLimits();
        }

        // 予約管理
        function addReservation() {
            const selectedDate = document.getElementById('selectedDate').value;
            const userName = document.getElementById('userName').value.trim();
            const purpose = document.getElementById('purpose').value.trim();

            if (!selectedDate || !userName) {
                showNotification('日付と予約者名は必須項目です', 'error');
                return;
            }

            if (reservations&#91;selectedDate]) {
                showNotification('この日は既に予約されています', 'error');
                return;
            }

            reservations&#91;selectedDate] = {
                userName: userName,
                purpose: purpose,
                reservedAt: new Date().toISOString()
            };

            resetForm();
            showNotification('予約が完了しました', 'success');
            showCalendar();
        }

        function cancelReservation(date) {
            if (confirm('この予約をキャンセルしますか?')) {
                delete reservations&#91;date];
                showNotification('予約がキャンセルされました', 'success');
                renderCalendar();
            }
        }

        function resetForm() {
            document.getElementById('selectedDate').value = '';
            document.getElementById('userName').value = '';
            document.getElementById('purpose').value = '';
        }

        // カレンダー表示
        function renderCalendar() {
            const content = document.getElementById('calendar-content');
            const dates = getWeekDates();
            const today = formatDate(new Date());
            
            content.innerHTML = '';

            dates.forEach(date => {
                const reservation = reservations&#91;date];
                const isToday = date === today;
                const isPast = new Date(date) &lt; new Date() &amp;&amp; !isToday;
                
                const dayCard = document.createElement('div');
                dayCard.className = `day-card ${
                    isPast ? 'expired' : 
                    reservation ? 'reserved' : 'available'
                }${isToday ? ' today' : ''}`;

                const statusText = reservation ? '予約済み' : 
                                 isPast ? '期限切れ' : '予約可能';
                const statusIcon = reservation ? '❌' : 
                                  isPast ? '⏰' : '✅';

                let dayCardHTML = '&lt;div class="day-header">';
                dayCardHTML += '&lt;div class="day-info">';
                dayCardHTML += '&lt;div class="day-date">';
                dayCardHTML += formatDisplayDate(date);
                if (isToday) {
                    dayCardHTML += '&lt;span style="color: #2196f3; font-size: 0.9rem; margin-left: 8px;">(今日)&lt;/span>';
                }
                dayCardHTML += '&lt;/div>';
                dayCardHTML += '&lt;div class="day-status ' + (isPast ? 'expired' : reservation ? 'reserved' : 'available') + '">';
                dayCardHTML += '&lt;span class="icon">' + statusIcon + '&lt;/span>';
                dayCardHTML += statusText;
                dayCardHTML += '&lt;/div>';
                dayCardHTML += '&lt;/div>';
                
                if (reservation &amp;&amp; !isPast) {
                    dayCardHTML += '&lt;button class="btn btn-danger" onclick="cancelReservation(\'' + date + '\')">';
                    dayCardHTML += 'キャンセル';
                    dayCardHTML += '&lt;/button>';
                }
                
                dayCardHTML += '&lt;/div>';
                
                if (reservation) {
                    dayCardHTML += '&lt;div class="reservation-details">';
                    dayCardHTML += '&lt;div class="reservation-item">';
                    dayCardHTML += '&lt;span class="icon">👤&lt;/span>';
                    dayCardHTML += '予約者: &lt;strong>' + reservation.userName + '&lt;/strong>';
                    dayCardHTML += '&lt;/div>';
                    
                    if (reservation.purpose) {
                        dayCardHTML += '&lt;div class="reservation-item">';
                        dayCardHTML += '&lt;span class="icon">📝&lt;/span>';
                        dayCardHTML += '用途: &lt;strong>' + reservation.purpose + '&lt;/strong>';
                        dayCardHTML += '&lt;/div>';
                    }
                    
                    dayCardHTML += '&lt;/div>';
                }
                
                dayCard.innerHTML = dayCardHTML;

                content.appendChild(dayCard);
            });
        }

        // 日付制限設定
        function setupDateLimits() {
            const today = new Date();
            const maxDate = new Date(today);
            maxDate.setDate(today.getDate() + 6);

            const dateInput = document.getElementById('selectedDate');
            dateInput.min = formatDate(today);
            dateInput.max = formatDate(maxDate);
        }

        // 初期化
        document.addEventListener('DOMContentLoaded', function() {
            showCalendar();
        });
    &lt;/script>
&lt;/body>
&lt;/html></code>

そして…完成しました!!
上記のコードで先ほどと同じ予約画面がブラウザにて立ち上がりました。
入力も問題なく出来、キャンセルボタンもしっかりと反応するのでやはりClaude内での画面で上手く動作していなかっただけになりますね。

まとめ

私はこれまでAIサービスをあまり活用出来てなかったのですが、最近ようやく使い出しとても便利なサービスだと今更ながら気づきました。
今回、単純な要件を指示しただけでもしっかりしたものが出来たのでもっと複雑な要件を提示したり、追加機能を実装させたりしても面白いと感じました。
今後はAIサービスを使用し業務の効率化が図れないかもっとAIと向き合っていきたいと思います。
皆さんも興味があれば是非Claudeを利用してみて下さい。
制限はありますが無料プランも用意されています。
https://claude.ai/