Upload folder using huggingface_hub
Browse files- .htaccess +13 -0
- LICENSE +21 -0
- README.md +228 -12
- api-functions.js +540 -0
- app.py +323 -0
- config.json +58 -0
- exports/.gitkeep +0 -0
- index.html +442 -0
- requirements.txt +1 -0
- supabase-config.js +149 -0
- workflows/.gitkeep +0 -0
.htaccess
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Disable Laravel routing for this directory
|
| 2 |
+
RewriteEngine Off
|
| 3 |
+
|
| 4 |
+
# Allow directory browsing (optional)
|
| 5 |
+
Options +Indexes
|
| 6 |
+
|
| 7 |
+
# Set index file
|
| 8 |
+
DirectoryIndex index.html
|
| 9 |
+
|
| 10 |
+
# Set MIME types
|
| 11 |
+
AddType text/html .html
|
| 12 |
+
AddType text/css .css
|
| 13 |
+
AddType application/javascript .js
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 API Workflow Builder Contributors
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
CHANGED
|
@@ -1,12 +1,228 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🚀 API Workflow Builder
|
| 2 |
+
|
| 3 |
+
Visual page builder with API integration powered by GrapeJS and Supabase.
|
| 4 |
+
|
| 5 |
+
## 📋 概要
|
| 6 |
+
|
| 7 |
+
ドラッグ&ドロップでAPIワークフロー画面を構築できるビジュアルエディタ。
|
| 8 |
+
Shop11 APIと連携し、商品・顧客・査定データを簡単に操作できます。
|
| 9 |
+
|
| 10 |
+
## ✨ 機能
|
| 11 |
+
|
| 12 |
+
### 📦 ページビルダー
|
| 13 |
+
- **ドラッグ&ドロップ**: GrapeJSベースの直感的なエディタ
|
| 14 |
+
- **リアルタイムプレビュー**: 編集内容をその場で確認
|
| 15 |
+
- **Supabase連携**: ページデータをクラウドに保存
|
| 16 |
+
- **HTML出力**: 作成したページをHTMLファイルとしてダウンロード
|
| 17 |
+
|
| 18 |
+
### 🔌 APIブロック
|
| 19 |
+
|
| 20 |
+
#### Shop11 API連携ブロック
|
| 21 |
+
1. **地金チェック削除** (`gold-check-delete`)
|
| 22 |
+
- エンドポイント: `POST /api/shouhin/gold_check_id_delete`
|
| 23 |
+
- 商品IDを指定して地金チェックを削除
|
| 24 |
+
|
| 25 |
+
2. **査定タイトル生成** (`satei-title`)
|
| 26 |
+
- エンドポイント: `GET /api/satei_func/create_title/{product_id}`
|
| 27 |
+
- 商品IDから査定タイトルを自動生成
|
| 28 |
+
|
| 29 |
+
3. **メール送信** (`send-notification`)
|
| 30 |
+
- エンドポイント: `POST /api/notification/mail`
|
| 31 |
+
- 宛先・件名・本文を指定してメール送信
|
| 32 |
+
|
| 33 |
+
4. **商品検索** (`product-search`)
|
| 34 |
+
- エンドポイント: `GET /api/auto_complete_refasta/ResultSearch`
|
| 35 |
+
- キーワードで商品を検索
|
| 36 |
+
|
| 37 |
+
5. **商品データテーブル** (`product-data-table`)
|
| 38 |
+
- 商品データを検索してテーブル表示
|
| 39 |
+
- ソート・フィルタリング対応
|
| 40 |
+
|
| 41 |
+
6. **顧客検索テーブル** (`customer-data-table`)
|
| 42 |
+
- 顧客名・電話番号で検索
|
| 43 |
+
- 顧客情報を一覧表示
|
| 44 |
+
|
| 45 |
+
7. **査定検索テーブル** (`satei-data-table`)
|
| 46 |
+
- 査定データを検索・表示
|
| 47 |
+
- ステータス別バッジ表示
|
| 48 |
+
|
| 49 |
+
8. **商品登録フォーム** (`product-register-form`)
|
| 50 |
+
- エンドポイント: `POST /api/shouhin/register`
|
| 51 |
+
- 新規商品をフォームから登録
|
| 52 |
+
|
| 53 |
+
### 💾 データ永続化
|
| 54 |
+
- **Supabase Tables**:
|
| 55 |
+
- `page_builder_pages`: ページデータ保存
|
| 56 |
+
- `api_execution_logs`: API実行履歴ログ
|
| 57 |
+
- **LocalStorage**: エディタの自動保存
|
| 58 |
+
|
| 59 |
+
## 🚀 使い方
|
| 60 |
+
|
| 61 |
+
### 1. アクセス
|
| 62 |
+
```
|
| 63 |
+
http://localhost/shop11/public/page-builder/
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
### 2. ブロックを追加
|
| 67 |
+
1. 左サイドバーから「Shop11 API」カテゴリを選択
|
| 68 |
+
2. 使いたいブロックをキャンバスにドラッグ&ドロップ
|
| 69 |
+
3. ブロック内の入力フォームに値を入力
|
| 70 |
+
4. ボタンをクリックしてAPI実行
|
| 71 |
+
|
| 72 |
+
### 3. ページを保存
|
| 73 |
+
1. ヘッダーの「💾 保存」ボタンをクリック
|
| 74 |
+
2. ページ名を入力
|
| 75 |
+
3. Supabaseに保存 + HTMLファイルをダウンロード
|
| 76 |
+
|
| 77 |
+
### 4. ページを読込
|
| 78 |
+
1. ヘッダーの「📂 読込」ボタンをクリック
|
| 79 |
+
2. 過去に保存したページ一覧から選択
|
| 80 |
+
3. エディタに読み込まれます
|
| 81 |
+
|
| 82 |
+
### 5. プレビュー
|
| 83 |
+
- 「👁️ プレビュー」ボタンで新しいタブで表示確認
|
| 84 |
+
|
| 85 |
+
## 📁 ファイル構成
|
| 86 |
+
|
| 87 |
+
```
|
| 88 |
+
page-builder/
|
| 89 |
+
├── index.html # メインエディタ画面
|
| 90 |
+
├── supabase-config.js # Supabase接続設定
|
| 91 |
+
├── api-functions.js # API呼び出し関数
|
| 92 |
+
├── .htaccess # Apache設定
|
| 93 |
+
└── README.md # このファイル
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
## 🔧 技術スタック
|
| 97 |
+
|
| 98 |
+
- **フロントエンド**: GrapeJS, Bootstrap 4.5.2, jQuery 3.5.1
|
| 99 |
+
- **バックエンド**: Laravel (Shop11), Supabase
|
| 100 |
+
- **データベース**: PostgreSQL (Supabase)
|
| 101 |
+
- **ストレージ**: Supabase Storage (将来実装予定)
|
| 102 |
+
|
| 103 |
+
## 🗄️ データベーススキーマ
|
| 104 |
+
|
| 105 |
+
### page_builder_pages
|
| 106 |
+
```sql
|
| 107 |
+
CREATE TABLE page_builder_pages (
|
| 108 |
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 109 |
+
name text NOT NULL,
|
| 110 |
+
html_content text,
|
| 111 |
+
css_content text,
|
| 112 |
+
components_json jsonb,
|
| 113 |
+
created_at timestamp with time zone DEFAULT now(),
|
| 114 |
+
updated_at timestamp with time zone DEFAULT now()
|
| 115 |
+
);
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
### api_execution_logs
|
| 119 |
+
```sql
|
| 120 |
+
CREATE TABLE api_execution_logs (
|
| 121 |
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 122 |
+
api_name text NOT NULL,
|
| 123 |
+
endpoint text NOT NULL,
|
| 124 |
+
request_data jsonb,
|
| 125 |
+
response_data jsonb,
|
| 126 |
+
status text CHECK (status IN ('success', 'error')),
|
| 127 |
+
executed_at timestamp with time zone DEFAULT now()
|
| 128 |
+
);
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
## 🔐 Supabase設定
|
| 132 |
+
|
| 133 |
+
### 環境変数
|
| 134 |
+
- **SUPABASE_URL**: `https://rootomzbucovwdqsscqd.supabase.co`
|
| 135 |
+
- **SUPABASE_ANON_KEY**: `eyJhbGci...` (supabase-config.jsに記載)
|
| 136 |
+
|
| 137 |
+
### RLS (Row Level Security)
|
| 138 |
+
現在はテスト用に全アクセス許可。本番環境では認証を追加してください。
|
| 139 |
+
|
| 140 |
+
## 🎨 カスタマイズ
|
| 141 |
+
|
| 142 |
+
### 新しいAPIブロックを追加
|
| 143 |
+
|
| 144 |
+
1. **index.htmlに登録**:
|
| 145 |
+
```javascript
|
| 146 |
+
blockManager.add('my-custom-block', {
|
| 147 |
+
label: '新しいブロック',
|
| 148 |
+
category: 'Shop11 API',
|
| 149 |
+
content: '<div class="shop11-api-block">...</div>'
|
| 150 |
+
});
|
| 151 |
+
```
|
| 152 |
+
|
| 153 |
+
2. **api-functions.jsに関数追加**:
|
| 154 |
+
```javascript
|
| 155 |
+
function myCustomFunction(button) {
|
| 156 |
+
const block = button.closest('.shop11-api-block');
|
| 157 |
+
// API呼び出し処理
|
| 158 |
+
fetch('/api/my-endpoint', {...})
|
| 159 |
+
.then(res => res.json())
|
| 160 |
+
.then(data => {
|
| 161 |
+
// Supabaseログ保存
|
| 162 |
+
if (window.saveApiLogToSupabase) {
|
| 163 |
+
saveApiLogToSupabase('API名', endpoint, requestData, data, 'success');
|
| 164 |
+
}
|
| 165 |
+
});
|
| 166 |
+
}
|
| 167 |
+
```
|
| 168 |
+
|
| 169 |
+
3. **イベントリスナーに登録**:
|
| 170 |
+
```javascript
|
| 171 |
+
case 'myCustomAction':
|
| 172 |
+
myCustomFunction(e.target);
|
| 173 |
+
break;
|
| 174 |
+
```
|
| 175 |
+
|
| 176 |
+
## 📊 API実行ログの確認
|
| 177 |
+
|
| 178 |
+
```javascript
|
| 179 |
+
// 直近50件のログを取得
|
| 180 |
+
getApiLogsFromSupabase(50).then(result => {
|
| 181 |
+
console.log(result.data);
|
| 182 |
+
});
|
| 183 |
+
```
|
| 184 |
+
|
| 185 |
+
または、Supabase Dashboardで直接確認:
|
| 186 |
+
```sql
|
| 187 |
+
SELECT * FROM api_execution_logs
|
| 188 |
+
ORDER BY executed_at DESC
|
| 189 |
+
LIMIT 50;
|
| 190 |
+
```
|
| 191 |
+
|
| 192 |
+
## 🐛 トラブルシューティング
|
| 193 |
+
|
| 194 |
+
### ブロックがドラッグできない
|
| 195 |
+
- ブラウザのキャッシュをクリア
|
| 196 |
+
- F5でページをリロード
|
| 197 |
+
|
| 198 |
+
### APIエラーが発生する
|
| 199 |
+
- CSRF Tokenが正しく設定されているか確認
|
| 200 |
+
- Laravel側のルーティングを確認
|
| 201 |
+
- ネットワークタブでHTTPステータスコードを確認
|
| 202 |
+
|
| 203 |
+
### Supabaseに保存できない
|
| 204 |
+
- Supabase接続情報が正しいか確認
|
| 205 |
+
- RLSポリシーでアクセスが許可されているか確認
|
| 206 |
+
- ブラウザコンソールでエラーメッセージを確認
|
| 207 |
+
|
| 208 |
+
## 🚧 今後の拡張予定
|
| 209 |
+
|
| 210 |
+
- [ ] Supabase Storageへのファイルアップロード
|
| 211 |
+
- [ ] 認証機能の追加
|
| 212 |
+
- [ ] ページバージョン管理
|
| 213 |
+
- [ ] コラボレーション機能
|
| 214 |
+
- [ ] カスタムコンポーネント作成UI
|
| 215 |
+
- [ ] ワークフロー自動化
|
| 216 |
+
- [ ] Hugging Face Spaceへのデプロイ
|
| 217 |
+
|
| 218 |
+
## 📝 ライセンス
|
| 219 |
+
|
| 220 |
+
MIT License (予定)
|
| 221 |
+
|
| 222 |
+
## 👤 作者
|
| 223 |
+
|
| 224 |
+
Created for API workflow automation and visual page building.
|
| 225 |
+
|
| 226 |
+
## 📞 サポート
|
| 227 |
+
|
| 228 |
+
問題が発生した場合は、ブラウザのコンソールログを確認してください。
|
api-functions.js
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Shop11 API Functions
|
| 2 |
+
// ページビルダーで作成したページで使用するAPI呼び出し関数
|
| 3 |
+
|
| 4 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 5 |
+
console.log('✅ Shop11 API Functions loaded');
|
| 6 |
+
|
| 7 |
+
// すべてのAPIボタンにイベントリスナーを追加
|
| 8 |
+
document.addEventListener('click', function(e) {
|
| 9 |
+
const action = e.target.getAttribute('data-action');
|
| 10 |
+
if (!action) return;
|
| 11 |
+
|
| 12 |
+
switch(action) {
|
| 13 |
+
case 'goldCheckDelete':
|
| 14 |
+
goldCheckDelete(e.target);
|
| 15 |
+
break;
|
| 16 |
+
case 'createSateiTitle':
|
| 17 |
+
createSateiTitle(e.target);
|
| 18 |
+
break;
|
| 19 |
+
case 'sendNotification':
|
| 20 |
+
sendNotification(e.target);
|
| 21 |
+
break;
|
| 22 |
+
case 'searchProduct':
|
| 23 |
+
searchProduct(e.target);
|
| 24 |
+
break;
|
| 25 |
+
case 'loadProductTable':
|
| 26 |
+
loadProductTable(e.target);
|
| 27 |
+
break;
|
| 28 |
+
case 'clearProductTable':
|
| 29 |
+
clearProductTable(e.target);
|
| 30 |
+
break;
|
| 31 |
+
case 'loadCustomerTable':
|
| 32 |
+
loadCustomerTable(e.target);
|
| 33 |
+
break;
|
| 34 |
+
case 'clearCustomerTable':
|
| 35 |
+
clearCustomerTable(e.target);
|
| 36 |
+
break;
|
| 37 |
+
case 'loadSateiTable':
|
| 38 |
+
loadSateiTable(e.target);
|
| 39 |
+
break;
|
| 40 |
+
case 'clearSateiTable':
|
| 41 |
+
clearSateiTable(e.target);
|
| 42 |
+
break;
|
| 43 |
+
case 'registerProduct':
|
| 44 |
+
registerProduct(e.target);
|
| 45 |
+
break;
|
| 46 |
+
}
|
| 47 |
+
});
|
| 48 |
+
});
|
| 49 |
+
|
| 50 |
+
// 地金チェック削除
|
| 51 |
+
function goldCheckDelete(button) {
|
| 52 |
+
const block = button.closest('.shop11-api-block');
|
| 53 |
+
const productId = block.querySelector('.gold-product-id').value;
|
| 54 |
+
const resultDiv = block.querySelector('.gold-check-result');
|
| 55 |
+
|
| 56 |
+
if (!productId) {
|
| 57 |
+
alert('商品IDを入力してください');
|
| 58 |
+
return;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
resultDiv.style.display = 'block';
|
| 62 |
+
resultDiv.innerHTML = '処理中...';
|
| 63 |
+
|
| 64 |
+
const requestData = { product_id: productId };
|
| 65 |
+
|
| 66 |
+
fetch('/api/shouhin/gold_check_id_delete', {
|
| 67 |
+
method: 'POST',
|
| 68 |
+
headers: {
|
| 69 |
+
'Content-Type': 'application/json',
|
| 70 |
+
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || ''
|
| 71 |
+
},
|
| 72 |
+
body: JSON.stringify(requestData)
|
| 73 |
+
})
|
| 74 |
+
.then(res => res.json())
|
| 75 |
+
.then(data => {
|
| 76 |
+
resultDiv.innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
|
| 77 |
+
|
| 78 |
+
// Supabaseにログ保存
|
| 79 |
+
if (window.saveApiLogToSupabase) {
|
| 80 |
+
saveApiLogToSupabase(
|
| 81 |
+
'地金チェック削除',
|
| 82 |
+
'/api/shouhin/gold_check_id_delete',
|
| 83 |
+
requestData,
|
| 84 |
+
data,
|
| 85 |
+
'success'
|
| 86 |
+
);
|
| 87 |
+
}
|
| 88 |
+
})
|
| 89 |
+
.catch(err => {
|
| 90 |
+
resultDiv.innerHTML = '<span style="color:red;">エラー: ' + err.message + '</span>';
|
| 91 |
+
|
| 92 |
+
// Supabaseにエラーログ保存
|
| 93 |
+
if (window.saveApiLogToSupabase) {
|
| 94 |
+
saveApiLogToSupabase(
|
| 95 |
+
'地金チェック削除',
|
| 96 |
+
'/api/shouhin/gold_check_id_delete',
|
| 97 |
+
requestData,
|
| 98 |
+
{ error: err.message },
|
| 99 |
+
'error'
|
| 100 |
+
);
|
| 101 |
+
}
|
| 102 |
+
});
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
// 査定タイトル生成
|
| 106 |
+
function createSateiTitle(button) {
|
| 107 |
+
const block = button.closest('.shop11-api-block');
|
| 108 |
+
const productId = block.querySelector('.satei-product-id').value;
|
| 109 |
+
const resultDiv = block.querySelector('.satei-title-result');
|
| 110 |
+
|
| 111 |
+
if (!productId) {
|
| 112 |
+
alert('商品IDを入力してください');
|
| 113 |
+
return;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
resultDiv.style.display = 'block';
|
| 117 |
+
resultDiv.innerHTML = '生成中...';
|
| 118 |
+
|
| 119 |
+
const endpoint = '/api/satei_func/create_title/' + productId;
|
| 120 |
+
const requestData = { product_id: productId };
|
| 121 |
+
|
| 122 |
+
fetch(endpoint, {
|
| 123 |
+
method: 'GET',
|
| 124 |
+
headers: {
|
| 125 |
+
'Content-Type': 'application/json'
|
| 126 |
+
}
|
| 127 |
+
})
|
| 128 |
+
.then(res => res.json())
|
| 129 |
+
.then(data => {
|
| 130 |
+
resultDiv.innerHTML = '<strong>タイトル:</strong><br>' + (data.title || JSON.stringify(data, null, 2));
|
| 131 |
+
|
| 132 |
+
if (window.saveApiLogToSupabase) {
|
| 133 |
+
saveApiLogToSupabase('査定タイトル生成', endpoint, requestData, data, 'success');
|
| 134 |
+
}
|
| 135 |
+
})
|
| 136 |
+
.catch(err => {
|
| 137 |
+
resultDiv.innerHTML = '<span style="color:red;">エラー: ' + err.message + '</span>';
|
| 138 |
+
|
| 139 |
+
if (window.saveApiLogToSupabase) {
|
| 140 |
+
saveApiLogToSupabase('査定タイトル生成', endpoint, requestData, { error: err.message }, 'error');
|
| 141 |
+
}
|
| 142 |
+
});
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
// メール送信
|
| 146 |
+
function sendNotification(button) {
|
| 147 |
+
const block = button.closest('.shop11-api-block');
|
| 148 |
+
const to = block.querySelector('.mail-to').value;
|
| 149 |
+
const subject = block.querySelector('.mail-subject').value;
|
| 150 |
+
const body = block.querySelector('.mail-body').value;
|
| 151 |
+
const resultDiv = block.querySelector('.mail-result');
|
| 152 |
+
|
| 153 |
+
if (!to || !subject || !body) {
|
| 154 |
+
alert('すべての項目を入力してください');
|
| 155 |
+
return;
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
resultDiv.style.display = 'block';
|
| 159 |
+
resultDiv.innerHTML = '送信中...';
|
| 160 |
+
|
| 161 |
+
const endpoint = '/api/notification/mail';
|
| 162 |
+
const requestData = { to, subject, body };
|
| 163 |
+
|
| 164 |
+
fetch(endpoint, {
|
| 165 |
+
method: 'POST',
|
| 166 |
+
headers: {
|
| 167 |
+
'Content-Type': 'application/json',
|
| 168 |
+
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || ''
|
| 169 |
+
},
|
| 170 |
+
body: JSON.stringify(requestData)
|
| 171 |
+
})
|
| 172 |
+
.then(res => res.json())
|
| 173 |
+
.then(data => {
|
| 174 |
+
resultDiv.innerHTML = '<span style="color:green;">✅ 送信完了</span><br><pre>' + JSON.stringify(data, null, 2) + '</pre>';
|
| 175 |
+
|
| 176 |
+
if (window.saveApiLogToSupabase) {
|
| 177 |
+
saveApiLogToSupabase('メール送信', endpoint, requestData, data, 'success');
|
| 178 |
+
}
|
| 179 |
+
})
|
| 180 |
+
.catch(err => {
|
| 181 |
+
resultDiv.innerHTML = '<span style="color:red;">❌ エラー: ' + err.message + '</span>';
|
| 182 |
+
|
| 183 |
+
if (window.saveApiLogToSupabase) {
|
| 184 |
+
saveApiLogToSupabase('メール送信', endpoint, requestData, { error: err.message }, 'error');
|
| 185 |
+
}
|
| 186 |
+
});
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
// 商品検索
|
| 190 |
+
function searchProduct(button) {
|
| 191 |
+
const block = button.closest('.shop11-api-block');
|
| 192 |
+
const keyword = block.querySelector('.search-keyword').value;
|
| 193 |
+
const resultDiv = block.querySelector('.search-result');
|
| 194 |
+
|
| 195 |
+
if (!keyword) {
|
| 196 |
+
alert('検索キーワードを入力してください');
|
| 197 |
+
return;
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
resultDiv.style.display = 'block';
|
| 201 |
+
resultDiv.innerHTML = '検索中...';
|
| 202 |
+
|
| 203 |
+
const endpoint = '/api/auto_complete_refasta/ResultSearch?q=' + encodeURIComponent(keyword);
|
| 204 |
+
const requestData = { keyword };
|
| 205 |
+
|
| 206 |
+
fetch(endpoint, {
|
| 207 |
+
method: 'GET',
|
| 208 |
+
headers: {
|
| 209 |
+
'Content-Type': 'application/json'
|
| 210 |
+
}
|
| 211 |
+
})
|
| 212 |
+
.then(res => res.json())
|
| 213 |
+
.then(data => {
|
| 214 |
+
if (Array.isArray(data) && data.length > 0) {
|
| 215 |
+
let html = '<ul style="list-style:none; padding:0;">';
|
| 216 |
+
data.forEach(item => {
|
| 217 |
+
html += '<li style="padding:10px; border-bottom:1px solid #eee;">' +
|
| 218 |
+
(item.label || item.name || JSON.stringify(item)) + '</li>';
|
| 219 |
+
});
|
| 220 |
+
html += '</ul>';
|
| 221 |
+
resultDiv.innerHTML = html;
|
| 222 |
+
} else {
|
| 223 |
+
resultDiv.innerHTML = '検索結果なし';
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
if (window.saveApiLogToSupabase) {
|
| 227 |
+
saveApiLogToSupabase('商品検索', endpoint, requestData, data, 'success');
|
| 228 |
+
}
|
| 229 |
+
})
|
| 230 |
+
.catch(err => {
|
| 231 |
+
resultDiv.innerHTML = '<span style="color:red;">エラー: ' + err.message + '</span>';
|
| 232 |
+
|
| 233 |
+
if (window.saveApiLogToSupabase) {
|
| 234 |
+
saveApiLogToSupabase('商品検索', endpoint, requestData, { error: err.message }, 'error');
|
| 235 |
+
}
|
| 236 |
+
});
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
// 商品データテーブル読込
|
| 240 |
+
function loadProductTable(button) {
|
| 241 |
+
const block = button.closest('.shop11-api-block');
|
| 242 |
+
const keyword = block.querySelector('.product-search-keyword').value;
|
| 243 |
+
const tableBody = block.querySelector('.product-table-body');
|
| 244 |
+
const infoDiv = block.querySelector('.product-table-info');
|
| 245 |
+
|
| 246 |
+
if (!keyword) {
|
| 247 |
+
alert('検索キーワードを入力してください');
|
| 248 |
+
return;
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
tableBody.innerHTML = '<tr><td colspan="7" class="text-center">🔍 検索中...</td></tr>';
|
| 252 |
+
|
| 253 |
+
const endpoint = '/api/auto_complete_refasta/ResultSearch?q=' + encodeURIComponent(keyword);
|
| 254 |
+
const requestData = { keyword };
|
| 255 |
+
|
| 256 |
+
fetch(endpoint, {
|
| 257 |
+
method: 'GET',
|
| 258 |
+
headers: {
|
| 259 |
+
'Content-Type': 'application/json'
|
| 260 |
+
}
|
| 261 |
+
})
|
| 262 |
+
.then(res => res.json())
|
| 263 |
+
.then(data => {
|
| 264 |
+
if (Array.isArray(data) && data.length > 0) {
|
| 265 |
+
let html = '';
|
| 266 |
+
data.forEach((item, index) => {
|
| 267 |
+
html += '<tr>';
|
| 268 |
+
html += '<td>' + (item.product_id || item.id || index + 1) + '</td>';
|
| 269 |
+
html += '<td>' + (item.label || item.name || item.title || '-') + '</td>';
|
| 270 |
+
html += '<td>' + (item.brand || item.maker || '-') + '</td>';
|
| 271 |
+
html += '<td>' + (item.category || item.category_name || '-') + '</td>';
|
| 272 |
+
html += '<td class="text-right">' + (item.price ? '¥' + parseInt(item.price).toLocaleString() : '-') + '</td>';
|
| 273 |
+
html += '<td>' + getStatusBadge(item.status) + '</td>';
|
| 274 |
+
html += '<td>' +
|
| 275 |
+
'<button class="btn btn-sm btn-info mr-1" onclick="alert(\'商品ID: ' + (item.product_id || item.id) + '\')">📝 詳細</button>' +
|
| 276 |
+
'<button class="btn btn-sm btn-warning" onclick="alert(\'編集機能は準備中です\')">✏️ 編集</button>' +
|
| 277 |
+
'</td>';
|
| 278 |
+
html += '</tr>';
|
| 279 |
+
});
|
| 280 |
+
tableBody.innerHTML = html;
|
| 281 |
+
infoDiv.innerHTML = '<small>✅ ' + data.length + '件の商品が見つかりました</small>';
|
| 282 |
+
|
| 283 |
+
if (window.saveApiLogToSupabase) {
|
| 284 |
+
saveApiLogToSupabase('商品データテーブル読込', endpoint, requestData, { count: data.length }, 'success');
|
| 285 |
+
}
|
| 286 |
+
} else {
|
| 287 |
+
tableBody.innerHTML = '<tr><td colspan="7" class="text-center text-muted">📭 検索結果なし</td></tr>';
|
| 288 |
+
infoDiv.innerHTML = '<small>検索結果: 0件</small>';
|
| 289 |
+
|
| 290 |
+
if (window.saveApiLogToSupabase) {
|
| 291 |
+
saveApiLogToSupabase('商品データテーブル読込', endpoint, requestData, { count: 0 }, 'success');
|
| 292 |
+
}
|
| 293 |
+
}
|
| 294 |
+
})
|
| 295 |
+
.catch(err => {
|
| 296 |
+
tableBody.innerHTML = '<tr><td colspan="7" class="text-center text-danger">❌ エラー: ' + err.message + '</td></tr>';
|
| 297 |
+
infoDiv.innerHTML = '<small class="text-danger">データ読込エラー</small>';
|
| 298 |
+
|
| 299 |
+
if (window.saveApiLogToSupabase) {
|
| 300 |
+
saveApiLogToSupabase('商品データテーブル読込', endpoint, requestData, { error: err.message }, 'error');
|
| 301 |
+
}
|
| 302 |
+
});
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
// 商品テーブルクリア
|
| 306 |
+
function clearProductTable(button) {
|
| 307 |
+
const block = button.closest('.shop11-api-block');
|
| 308 |
+
const keyword = block.querySelector('.product-search-keyword');
|
| 309 |
+
const tableBody = block.querySelector('.product-table-body');
|
| 310 |
+
const infoDiv = block.querySelector('.product-table-info');
|
| 311 |
+
|
| 312 |
+
keyword.value = '';
|
| 313 |
+
tableBody.innerHTML = '<tr><td colspan="7" class="text-center text-muted">検索キーワードを入力して検索してください</td></tr>';
|
| 314 |
+
infoDiv.innerHTML = '';
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
// ステータスバッジ生成
|
| 318 |
+
function getStatusBadge(status) {
|
| 319 |
+
if (!status) return '<span class="badge badge-secondary">-</span>';
|
| 320 |
+
|
| 321 |
+
const statusMap = {
|
| 322 |
+
'1': { label: '在庫あり', color: 'success' },
|
| 323 |
+
'2': { label: '予約中', color: 'warning' },
|
| 324 |
+
'3': { label: '売約済', color: 'danger' },
|
| 325 |
+
'4': { label: '出品中', color: 'info' },
|
| 326 |
+
'5': { label: '取り下げ', color: 'secondary' }
|
| 327 |
+
};
|
| 328 |
+
|
| 329 |
+
const statusInfo = statusMap[status] || { label: status, color: 'secondary' };
|
| 330 |
+
return '<span class="badge badge-' + statusInfo.color + '">' + statusInfo.label + '</span>';
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
// 顧客データテーブル読込
|
| 334 |
+
function loadCustomerTable(button) {
|
| 335 |
+
const block = button.closest('.shop11-api-block');
|
| 336 |
+
const keyword = block.querySelector('.customer-search-keyword').value;
|
| 337 |
+
const tableBody = block.querySelector('.customer-table-body');
|
| 338 |
+
const infoDiv = block.querySelector('.customer-table-info');
|
| 339 |
+
|
| 340 |
+
if (!keyword) {
|
| 341 |
+
alert('検索キーワードを入力してください');
|
| 342 |
+
return;
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
tableBody.innerHTML = '<tr><td colspan="7" class="text-center">🔍 検索中...</td></tr>';
|
| 346 |
+
|
| 347 |
+
const endpoint = '/api/Eoc/search?keyword=' + encodeURIComponent(keyword);
|
| 348 |
+
const requestData = { keyword };
|
| 349 |
+
|
| 350 |
+
fetch(endpoint, {
|
| 351 |
+
method: 'GET',
|
| 352 |
+
headers: { 'Content-Type': 'application/json' }
|
| 353 |
+
})
|
| 354 |
+
.then(res => res.json())
|
| 355 |
+
.then(data => {
|
| 356 |
+
const customers = Array.isArray(data) ? data : (data.data || []);
|
| 357 |
+
|
| 358 |
+
if (customers.length > 0) {
|
| 359 |
+
let html = '';
|
| 360 |
+
customers.forEach(customer => {
|
| 361 |
+
html += '<tr>';
|
| 362 |
+
html += '<td>' + (customer.ecc_id || customer.id || '-') + '</td>';
|
| 363 |
+
html += '<td>' + (customer.name1 || customer.name || '-') + '</td>';
|
| 364 |
+
html += '<td>' + (customer.tel || customer.phone || '-') + '</td>';
|
| 365 |
+
html += '<td>' + (customer.mail1 || customer.email || '-') + '</td>';
|
| 366 |
+
html += '<td>' + (customer.zip1 || customer.zip || '-') + '</td>';
|
| 367 |
+
html += '<td>' + (customer.address1 || customer.address || '-') + '</td>';
|
| 368 |
+
html += '<td>' +
|
| 369 |
+
'<button class="btn btn-sm btn-info mr-1" onclick="alert(\'顧客ID: ' + (customer.ecc_id || customer.id) + '\')">📋 詳細</button>' +
|
| 370 |
+
'<button class="btn btn-sm btn-primary" onclick="alert(\'編集機能は準備中です\')">✏️ 編集</button>' +
|
| 371 |
+
'</td>';
|
| 372 |
+
html += '</tr>';
|
| 373 |
+
});
|
| 374 |
+
tableBody.innerHTML = html;
|
| 375 |
+
infoDiv.innerHTML = '<small>✅ ' + customers.length + '件の顧客が見つかりました</small>';
|
| 376 |
+
|
| 377 |
+
if (window.saveApiLogToSupabase) {
|
| 378 |
+
saveApiLogToSupabase('顧客検索テーブル', endpoint, requestData, { count: customers.length }, 'success');
|
| 379 |
+
}
|
| 380 |
+
} else {
|
| 381 |
+
tableBody.innerHTML = '<tr><td colspan="7" class="text-center text-muted">📭 検索結果なし</td></tr>';
|
| 382 |
+
infoDiv.innerHTML = '<small>検索結果: 0件</small>';
|
| 383 |
+
}
|
| 384 |
+
})
|
| 385 |
+
.catch(err => {
|
| 386 |
+
tableBody.innerHTML = '<tr><td colspan="7" class="text-center text-danger">❌ エラー: ' + err.message + '</td></tr>';
|
| 387 |
+
infoDiv.innerHTML = '<small class="text-danger">データ読込エラー</small>';
|
| 388 |
+
|
| 389 |
+
if (window.saveApiLogToSupabase) {
|
| 390 |
+
saveApiLogToSupabase('顧客検索テーブル', endpoint, requestData, { error: err.message }, 'error');
|
| 391 |
+
}
|
| 392 |
+
});
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
// 顧客テーブルクリア
|
| 396 |
+
function clearCustomerTable(button) {
|
| 397 |
+
const block = button.closest('.shop11-api-block');
|
| 398 |
+
block.querySelector('.customer-search-keyword').value = '';
|
| 399 |
+
block.querySelector('.customer-table-body').innerHTML = '<tr><td colspan="7" class="text-center text-muted">検索キーワードを入力して検索してください</td></tr>';
|
| 400 |
+
block.querySelector('.customer-table-info').innerHTML = '';
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
// 査定データテーブル読込
|
| 404 |
+
function loadSateiTable(button) {
|
| 405 |
+
const block = button.closest('.shop11-api-block');
|
| 406 |
+
const keyword = block.querySelector('.satei-search-keyword').value;
|
| 407 |
+
const tableBody = block.querySelector('.satei-table-body');
|
| 408 |
+
const infoDiv = block.querySelector('.satei-table-info');
|
| 409 |
+
|
| 410 |
+
if (!keyword) {
|
| 411 |
+
alert('検索キーワードを入力してください');
|
| 412 |
+
return;
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
tableBody.innerHTML = '<tr><td colspan="7" class="text-center">🔍 検索中...</td></tr>';
|
| 416 |
+
|
| 417 |
+
const endpoint = '/api/satei/search?keyword=' + encodeURIComponent(keyword);
|
| 418 |
+
const requestData = { keyword };
|
| 419 |
+
|
| 420 |
+
fetch(endpoint, {
|
| 421 |
+
method: 'GET',
|
| 422 |
+
headers: { 'Content-Type': 'application/json' }
|
| 423 |
+
})
|
| 424 |
+
.then(res => res.json())
|
| 425 |
+
.then(data => {
|
| 426 |
+
const sateiList = Array.isArray(data) ? data : (data.data || []);
|
| 427 |
+
|
| 428 |
+
if (sateiList.length > 0) {
|
| 429 |
+
let html = '';
|
| 430 |
+
sateiList.forEach(satei => {
|
| 431 |
+
html += '<tr>';
|
| 432 |
+
html += '<td>' + (satei.id || '-') + '</td>';
|
| 433 |
+
html += '<td>' + (satei.product_name || satei.title || '-') + '</td>';
|
| 434 |
+
html += '<td>' + (satei.customer_name || satei.ecc_name || '-') + '</td>';
|
| 435 |
+
html += '<td class="text-right">' + (satei.price ? '¥' + parseInt(satei.price).toLocaleString() : '-') + '</td>';
|
| 436 |
+
html += '<td>' + (satei.satei_date || satei.created_at || '-') + '</td>';
|
| 437 |
+
html += '<td>' + getSateiStatusBadge(satei.status) + '</td>';
|
| 438 |
+
html += '<td>' +
|
| 439 |
+
'<button class="btn btn-sm btn-info mr-1" onclick="alert(\'査定ID: ' + (satei.id) + '\')">📋 詳細</button>' +
|
| 440 |
+
'<button class="btn btn-sm btn-warning" onclick="alert(\'編集機能は準備中です\')">✏️ 編集</button>' +
|
| 441 |
+
'</td>';
|
| 442 |
+
html += '</tr>';
|
| 443 |
+
});
|
| 444 |
+
tableBody.innerHTML = html;
|
| 445 |
+
infoDiv.innerHTML = '<small>✅ ' + sateiList.length + '件の査定が見つかりました</small>';
|
| 446 |
+
|
| 447 |
+
if (window.saveApiLogToSupabase) {
|
| 448 |
+
saveApiLogToSupabase('査定検索テーブル', endpoint, requestData, { count: sateiList.length }, 'success');
|
| 449 |
+
}
|
| 450 |
+
} else {
|
| 451 |
+
tableBody.innerHTML = '<tr><td colspan="7" class="text-center text-muted">📭 検索結果なし</td></tr>';
|
| 452 |
+
infoDiv.innerHTML = '<small>検索結果: 0件</small>';
|
| 453 |
+
}
|
| 454 |
+
})
|
| 455 |
+
.catch(err => {
|
| 456 |
+
tableBody.innerHTML = '<tr><td colspan="7" class="text-center text-danger">❌ エラー: ' + err.message + '</td></tr>';
|
| 457 |
+
infoDiv.innerHTML = '<small class="text-danger">データ読込エラー</small>';
|
| 458 |
+
|
| 459 |
+
if (window.saveApiLogToSupabase) {
|
| 460 |
+
saveApiLogToSupabase('査定検索テーブル', endpoint, requestData, { error: err.message }, 'error');
|
| 461 |
+
}
|
| 462 |
+
});
|
| 463 |
+
}
|
| 464 |
+
|
| 465 |
+
// 査定テーブルクリア
|
| 466 |
+
function clearSateiTable(button) {
|
| 467 |
+
const block = button.closest('.shop11-api-block');
|
| 468 |
+
block.querySelector('.satei-search-keyword').value = '';
|
| 469 |
+
block.querySelector('.satei-table-body').innerHTML = '<tr><td colspan="7" class="text-center text-muted">検索キーワードを入力して検索してください</td></tr>';
|
| 470 |
+
block.querySelector('.satei-table-info').innerHTML = '';
|
| 471 |
+
}
|
| 472 |
+
|
| 473 |
+
// 査定ステータスバッジ
|
| 474 |
+
function getSateiStatusBadge(status) {
|
| 475 |
+
const statusMap = {
|
| 476 |
+
'1': { label: '査定中', color: 'info' },
|
| 477 |
+
'2': { label: '査定完了', color: 'success' },
|
| 478 |
+
'3': { label: '買取完了', color: 'primary' },
|
| 479 |
+
'4': { label: 'キ��ンセル', color: 'danger' }
|
| 480 |
+
};
|
| 481 |
+
const statusInfo = statusMap[status] || { label: status || '-', color: 'secondary' };
|
| 482 |
+
return '<span class="badge badge-' + statusInfo.color + '">' + statusInfo.label + '</span>';
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
// 商品登録
|
| 486 |
+
function registerProduct(button) {
|
| 487 |
+
const block = button.closest('.shop11-api-block');
|
| 488 |
+
const form = block.querySelector('.product-register-form');
|
| 489 |
+
const resultDiv = block.querySelector('.register-result');
|
| 490 |
+
|
| 491 |
+
const name = block.querySelector('.product-name').value;
|
| 492 |
+
const brand = block.querySelector('.product-brand').value;
|
| 493 |
+
const category = block.querySelector('.product-category').value;
|
| 494 |
+
const price = block.querySelector('.product-price').value;
|
| 495 |
+
const description = block.querySelector('.product-description').value;
|
| 496 |
+
const status = block.querySelector('.product-status').value;
|
| 497 |
+
|
| 498 |
+
if (!name || !price) {
|
| 499 |
+
alert('商品名と価格は必須です');
|
| 500 |
+
return;
|
| 501 |
+
}
|
| 502 |
+
|
| 503 |
+
resultDiv.style.display = 'block';
|
| 504 |
+
resultDiv.innerHTML = '💾 登録中...';
|
| 505 |
+
|
| 506 |
+
const endpoint = '/api/shouhin/register';
|
| 507 |
+
const requestData = {
|
| 508 |
+
name: name,
|
| 509 |
+
brand: brand,
|
| 510 |
+
category: category,
|
| 511 |
+
price: price,
|
| 512 |
+
description: description,
|
| 513 |
+
status: status
|
| 514 |
+
};
|
| 515 |
+
|
| 516 |
+
fetch(endpoint, {
|
| 517 |
+
method: 'POST',
|
| 518 |
+
headers: {
|
| 519 |
+
'Content-Type': 'application/json',
|
| 520 |
+
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || ''
|
| 521 |
+
},
|
| 522 |
+
body: JSON.stringify(requestData)
|
| 523 |
+
})
|
| 524 |
+
.then(res => res.json())
|
| 525 |
+
.then(data => {
|
| 526 |
+
resultDiv.innerHTML = '<div class="alert alert-success">✅ 商品を登録しました!<br>商品ID: ' + (data.product_id || data.id || '不明') + '</div>';
|
| 527 |
+
form.reset();
|
| 528 |
+
|
| 529 |
+
if (window.saveApiLogToSupabase) {
|
| 530 |
+
saveApiLogToSupabase('商品登録', endpoint, requestData, data, 'success');
|
| 531 |
+
}
|
| 532 |
+
})
|
| 533 |
+
.catch(err => {
|
| 534 |
+
resultDiv.innerHTML = '<div class="alert alert-danger">❌ エラー: ' + err.message + '</div>';
|
| 535 |
+
|
| 536 |
+
if (window.saveApiLogToSupabase) {
|
| 537 |
+
saveApiLogToSupabase('商品登録', endpoint, requestData, { error: err.message }, 'error');
|
| 538 |
+
}
|
| 539 |
+
});
|
| 540 |
+
}
|
app.py
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import json
|
| 3 |
+
import os
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
|
| 6 |
+
# Gradio App for API Workflow Builder
|
| 7 |
+
# Visual page builder with drag-and-drop interface
|
| 8 |
+
|
| 9 |
+
def load_page_builder():
|
| 10 |
+
"""Load the main page builder HTML"""
|
| 11 |
+
with open('index.html', 'r', encoding='utf-8') as f:
|
| 12 |
+
return f.read()
|
| 13 |
+
|
| 14 |
+
def save_workflow(workflow_name, html_content, css_content):
|
| 15 |
+
"""Save workflow to JSON file"""
|
| 16 |
+
if not workflow_name:
|
| 17 |
+
return "❌ ワークフロー名を入力してください"
|
| 18 |
+
|
| 19 |
+
workflows_dir = 'workflows'
|
| 20 |
+
os.makedirs(workflows_dir, exist_ok=True)
|
| 21 |
+
|
| 22 |
+
workflow_data = {
|
| 23 |
+
'name': workflow_name,
|
| 24 |
+
'html': html_content,
|
| 25 |
+
'css': css_content,
|
| 26 |
+
'created_at': datetime.now().isoformat(),
|
| 27 |
+
'updated_at': datetime.now().isoformat()
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
filename = f"{workflows_dir}/{workflow_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
| 31 |
+
|
| 32 |
+
with open(filename, 'w', encoding='utf-8') as f:
|
| 33 |
+
json.dump(workflow_data, f, ensure_ascii=False, indent=2)
|
| 34 |
+
|
| 35 |
+
return f"✅ ワークフロー保存完了: {filename}"
|
| 36 |
+
|
| 37 |
+
def load_workflows():
|
| 38 |
+
"""Load list of saved workflows"""
|
| 39 |
+
workflows_dir = 'workflows'
|
| 40 |
+
if not os.path.exists(workflows_dir):
|
| 41 |
+
return []
|
| 42 |
+
|
| 43 |
+
workflows = []
|
| 44 |
+
for filename in os.listdir(workflows_dir):
|
| 45 |
+
if filename.endswith('.json'):
|
| 46 |
+
with open(f"{workflows_dir}/{filename}", 'r', encoding='utf-8') as f:
|
| 47 |
+
data = json.load(f)
|
| 48 |
+
workflows.append({
|
| 49 |
+
'filename': filename,
|
| 50 |
+
'name': data.get('name', 'Unknown'),
|
| 51 |
+
'created_at': data.get('created_at', 'Unknown')
|
| 52 |
+
})
|
| 53 |
+
|
| 54 |
+
return workflows
|
| 55 |
+
|
| 56 |
+
def export_html(workflow_name, html_content, css_content):
|
| 57 |
+
"""Export complete HTML file"""
|
| 58 |
+
full_html = f"""<!DOCTYPE html>
|
| 59 |
+
<html lang="ja">
|
| 60 |
+
<head>
|
| 61 |
+
<meta charset="UTF-8">
|
| 62 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 63 |
+
<title>{workflow_name}</title>
|
| 64 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/css/bootstrap.min.css">
|
| 65 |
+
<style>{css_content}</style>
|
| 66 |
+
</head>
|
| 67 |
+
<body>
|
| 68 |
+
{html_content}
|
| 69 |
+
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
|
| 70 |
+
</body>
|
| 71 |
+
</html>"""
|
| 72 |
+
|
| 73 |
+
output_dir = 'exports'
|
| 74 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 75 |
+
|
| 76 |
+
filename = f"{output_dir}/{workflow_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html"
|
| 77 |
+
|
| 78 |
+
with open(filename, 'w', encoding='utf-8') as f:
|
| 79 |
+
f.write(full_html)
|
| 80 |
+
|
| 81 |
+
return filename
|
| 82 |
+
|
| 83 |
+
# Gradio Interface
|
| 84 |
+
with gr.Blocks(title="🚀 API Workflow Builder", theme=gr.themes.Soft()) as app:
|
| 85 |
+
gr.Markdown("""
|
| 86 |
+
# 🚀 API Workflow Builder
|
| 87 |
+
|
| 88 |
+
Visual page builder with drag-and-drop interface powered by GrapeJS.
|
| 89 |
+
Create API workflow screens without coding!
|
| 90 |
+
|
| 91 |
+
## ✨ Features
|
| 92 |
+
- 📦 Drag & Drop page builder
|
| 93 |
+
- 🔌 API integration blocks
|
| 94 |
+
- 💾 Save workflows to JSON/Supabase
|
| 95 |
+
- 📥 Export as HTML file
|
| 96 |
+
- 🎨 Visual styling editor
|
| 97 |
+
""")
|
| 98 |
+
|
| 99 |
+
with gr.Tab("📝 Page Builder"):
|
| 100 |
+
gr.Markdown("### Visual Editor")
|
| 101 |
+
gr.Markdown("Open the editor below to start building your workflow pages.")
|
| 102 |
+
|
| 103 |
+
# Page Builder iframe
|
| 104 |
+
page_builder_html = gr.HTML(
|
| 105 |
+
value="""
|
| 106 |
+
<div style="width: 100%; height: 800px; border: 2px solid #e2e8f0; border-radius: 8px; overflow: hidden;">
|
| 107 |
+
<iframe
|
| 108 |
+
src="/file=index.html"
|
| 109 |
+
style="width: 100%; height: 100%; border: none;"
|
| 110 |
+
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
|
| 111 |
+
></iframe>
|
| 112 |
+
</div>
|
| 113 |
+
""",
|
| 114 |
+
label="Page Builder Editor"
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
gr.Markdown("""
|
| 118 |
+
### 使い方
|
| 119 |
+
1. 左サイドバーから要素をドラッグ&ドロップ
|
| 120 |
+
2. 右サイドバーでスタイルをカスタマイズ
|
| 121 |
+
3. ヘッダーの「保存」ボタンでSupabaseに保存
|
| 122 |
+
4. 下の「エクスポート」タブからHTMLファイルをダウンロード
|
| 123 |
+
""")
|
| 124 |
+
|
| 125 |
+
with gr.Tab("💾 Workflows"):
|
| 126 |
+
gr.Markdown("### 保存されたワークフロー")
|
| 127 |
+
|
| 128 |
+
with gr.Row():
|
| 129 |
+
workflow_name_input = gr.Textbox(
|
| 130 |
+
label="ワークフロー名",
|
| 131 |
+
placeholder="my-workflow"
|
| 132 |
+
)
|
| 133 |
+
save_btn = gr.Button("💾 保存", variant="primary")
|
| 134 |
+
|
| 135 |
+
save_status = gr.Textbox(label="保存ステータス", interactive=False)
|
| 136 |
+
|
| 137 |
+
gr.Markdown("### ワークフロー一覧")
|
| 138 |
+
workflows_list = gr.JSON(label="保存済みワークフロー")
|
| 139 |
+
|
| 140 |
+
refresh_btn = gr.Button("🔄 更新")
|
| 141 |
+
|
| 142 |
+
# Event handlers
|
| 143 |
+
refresh_btn.click(
|
| 144 |
+
fn=lambda: load_workflows(),
|
| 145 |
+
outputs=workflows_list
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
with gr.Tab("📥 Export"):
|
| 149 |
+
gr.Markdown("### HTMLエクスポート")
|
| 150 |
+
|
| 151 |
+
with gr.Row():
|
| 152 |
+
export_name = gr.Textbox(
|
| 153 |
+
label="ファイル名",
|
| 154 |
+
placeholder="my-page"
|
| 155 |
+
)
|
| 156 |
+
html_input = gr.Textbox(
|
| 157 |
+
label="HTML Content",
|
| 158 |
+
placeholder="<div>...</div>",
|
| 159 |
+
lines=5
|
| 160 |
+
)
|
| 161 |
+
css_input = gr.Textbox(
|
| 162 |
+
label="CSS Content",
|
| 163 |
+
placeholder=".class { ... }",
|
| 164 |
+
lines=5
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
export_btn = gr.Button("📥 HTMLエクスポート", variant="primary")
|
| 168 |
+
export_result = gr.File(label="ダウンロード")
|
| 169 |
+
|
| 170 |
+
export_btn.click(
|
| 171 |
+
fn=export_html,
|
| 172 |
+
inputs=[export_name, html_input, css_input],
|
| 173 |
+
outputs=export_result
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
with gr.Tab("⚙️ Settings"):
|
| 177 |
+
gr.Markdown("### API設定")
|
| 178 |
+
|
| 179 |
+
with gr.Row():
|
| 180 |
+
with gr.Column():
|
| 181 |
+
supabase_url = gr.Textbox(
|
| 182 |
+
label="Supabase URL",
|
| 183 |
+
value="https://rootomzbucovwdqsscqd.supabase.co"
|
| 184 |
+
)
|
| 185 |
+
supabase_key = gr.Textbox(
|
| 186 |
+
label="Supabase Anon Key",
|
| 187 |
+
value="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
| 188 |
+
type="password"
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
with gr.Column():
|
| 192 |
+
api_base_url = gr.Textbox(
|
| 193 |
+
label="API Base URL",
|
| 194 |
+
value="https://your-api.com",
|
| 195 |
+
placeholder="https://api.example.com"
|
| 196 |
+
)
|
| 197 |
+
api_auth_type = gr.Dropdown(
|
| 198 |
+
label="認証タイプ",
|
| 199 |
+
choices=["None", "Bearer Token", "API Key", "Basic Auth"],
|
| 200 |
+
value="None"
|
| 201 |
+
)
|
| 202 |
+
|
| 203 |
+
save_config_btn = gr.Button("💾 設定保存", variant="primary")
|
| 204 |
+
config_status = gr.Textbox(label="保存ステータス", interactive=False)
|
| 205 |
+
|
| 206 |
+
with gr.Tab("📚 Documentation"):
|
| 207 |
+
gr.Markdown("""
|
| 208 |
+
## 📖 ドキュメント
|
| 209 |
+
|
| 210 |
+
### 🎯 概要
|
| 211 |
+
API Workflow Builderは、ドラッグ&ドロップでAPIワークフロー画面を作成できるノーコードツールです。
|
| 212 |
+
|
| 213 |
+
### 🚀 クイックスタート
|
| 214 |
+
|
| 215 |
+
1. **Page Builderタブ**を開く
|
| 216 |
+
2. 左サイドバーから要素をドラッグ
|
| 217 |
+
3. キャンバスにドロップして配置
|
| 218 |
+
4. スタイルをカスタマイズ
|
| 219 |
+
5. 保存またはエクスポート
|
| 220 |
+
|
| 221 |
+
### 📦 利用可能なブロック
|
| 222 |
+
|
| 223 |
+
#### Shop11 API カテゴリ
|
| 224 |
+
- **地金チェック削除**: 商品IDで地金チェックを削除
|
| 225 |
+
- **査定タイトル生成**: 商品IDから査定タイトルを生成
|
| 226 |
+
- **メール送信**: メール通知を送信
|
| 227 |
+
- **商品検索**: キーワードで商品を検索
|
| 228 |
+
- **商品データテーブル**: 商品データを検索・表示
|
| 229 |
+
- **顧客検索テーブル**: 顧客情報を検索・表示
|
| 230 |
+
- **査定検索テーブル**: 査定データを検索・表示
|
| 231 |
+
- **商品登録フォーム**: 新規商品を登録
|
| 232 |
+
|
| 233 |
+
### 🔌 API連携
|
| 234 |
+
|
| 235 |
+
すべてのAPIブロックは自動的にSupabaseにログを保存します。
|
| 236 |
+
|
| 237 |
+
```javascript
|
| 238 |
+
// API呼び出し例
|
| 239 |
+
fetch('/api/your-endpoint', {
|
| 240 |
+
method: 'POST',
|
| 241 |
+
headers: { 'Content-Type': 'application/json' },
|
| 242 |
+
body: JSON.stringify(data)
|
| 243 |
+
})
|
| 244 |
+
.then(res => res.json())
|
| 245 |
+
.then(data => {
|
| 246 |
+
// Supabaseログ保存
|
| 247 |
+
saveApiLogToSupabase('API名', endpoint, request, response, 'success');
|
| 248 |
+
});
|
| 249 |
+
```
|
| 250 |
+
|
| 251 |
+
### 💾 データ保存
|
| 252 |
+
|
| 253 |
+
ページデータは以下の形式でSupabaseに保存されます:
|
| 254 |
+
|
| 255 |
+
```json
|
| 256 |
+
{
|
| 257 |
+
"name": "page-name",
|
| 258 |
+
"html_content": "<div>...</div>",
|
| 259 |
+
"css_content": ".class {...}",
|
| 260 |
+
"components_json": {...},
|
| 261 |
+
"created_at": "2026-02-23T...",
|
| 262 |
+
"updated_at": "2026-02-23T..."
|
| 263 |
+
}
|
| 264 |
+
```
|
| 265 |
+
|
| 266 |
+
### 🎨 カスタマイズ
|
| 267 |
+
|
| 268 |
+
新しいブロックを追加するには:
|
| 269 |
+
|
| 270 |
+
1. `index.html`の`blockManager.add()`に新しいブロックを登録
|
| 271 |
+
2. `api-functions.js`にAPI呼び出し関数を追加
|
| 272 |
+
3. イベントリスナーに`data-action`を登録
|
| 273 |
+
|
| 274 |
+
### 🔐 セキュリティ
|
| 275 |
+
|
| 276 |
+
- Supabase RLSポリシーで行レベルセキュリティを設定
|
| 277 |
+
- API Keyは環境変数で管理
|
| 278 |
+
- CSRF Tokenを使用したAPI保護
|
| 279 |
+
|
| 280 |
+
### 📊 技術スタック
|
| 281 |
+
|
| 282 |
+
- **Frontend**: GrapeJS, Bootstrap, jQuery
|
| 283 |
+
- **Backend**: Gradio (Python)
|
| 284 |
+
- **Database**: Supabase (PostgreSQL)
|
| 285 |
+
- **Hosting**: Hugging Face Spaces
|
| 286 |
+
|
| 287 |
+
### 🐛 トラブルシューティング
|
| 288 |
+
|
| 289 |
+
**ブロックがドラッグできない**
|
| 290 |
+
- ブラウザのキャッシュをクリア
|
| 291 |
+
- ページをリロード
|
| 292 |
+
|
| 293 |
+
**APIエラー**
|
| 294 |
+
- ネットワークタブでHTTPステータス確認
|
| 295 |
+
- Supabase接続情報を確認
|
| 296 |
+
|
| 297 |
+
**保存できない**
|
| 298 |
+
- RLSポリシーを確認
|
| 299 |
+
- ブラウザコンソールのエラーログをチェック
|
| 300 |
+
|
| 301 |
+
### 📝 ライセンス
|
| 302 |
+
|
| 303 |
+
MIT License
|
| 304 |
+
|
| 305 |
+
### 👤 作者
|
| 306 |
+
|
| 307 |
+
Created for visual API workflow automation.
|
| 308 |
+
|
| 309 |
+
### 🔗 リンク
|
| 310 |
+
|
| 311 |
+
- [GitHub Repository](https://github.com/your-repo)
|
| 312 |
+
- [Supabase Docs](https://supabase.com/docs)
|
| 313 |
+
- [GrapeJS Docs](https://grapesjs.com/docs/)
|
| 314 |
+
""")
|
| 315 |
+
|
| 316 |
+
# Launch app
|
| 317 |
+
if __name__ == "__main__":
|
| 318 |
+
app.launch(
|
| 319 |
+
server_name="0.0.0.0",
|
| 320 |
+
server_port=7860,
|
| 321 |
+
share=False,
|
| 322 |
+
show_error=True
|
| 323 |
+
)
|
config.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"app": {
|
| 3 |
+
"name": "API Workflow Builder",
|
| 4 |
+
"version": "1.0.0",
|
| 5 |
+
"description": "Visual page builder for API workflows"
|
| 6 |
+
},
|
| 7 |
+
"supabase": {
|
| 8 |
+
"url": "https://rootomzbucovwdqsscqd.supabase.co",
|
| 9 |
+
"anonKey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJvb3RvbXpidWNvdndkcXNzY3FkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzU4OTE4ODMsImV4cCI6MjA1MTQ2Nzg4M30.fYKOe-HPh4WUdvBhEJxakLWCMQBp4E90EDwARk7ucf8",
|
| 10 |
+
"tables": {
|
| 11 |
+
"pages": "page_builder_pages",
|
| 12 |
+
"logs": "api_execution_logs"
|
| 13 |
+
}
|
| 14 |
+
},
|
| 15 |
+
"api": {
|
| 16 |
+
"baseUrl": "https://your-api.com",
|
| 17 |
+
"authType": "none",
|
| 18 |
+
"authToken": "",
|
| 19 |
+
"endpoints": {
|
| 20 |
+
"productSearch": "/api/products/search",
|
| 21 |
+
"customerSearch": "/api/customers/search",
|
| 22 |
+
"assessmentSearch": "/api/assessments/search",
|
| 23 |
+
"productRegister": "/api/products/register",
|
| 24 |
+
"emailNotification": "/api/notifications/email"
|
| 25 |
+
}
|
| 26 |
+
},
|
| 27 |
+
"blocks": {
|
| 28 |
+
"categories": [
|
| 29 |
+
{
|
| 30 |
+
"id": "api",
|
| 31 |
+
"label": "API Blocks",
|
| 32 |
+
"enabled": true
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"id": "ui",
|
| 36 |
+
"label": "UI Components",
|
| 37 |
+
"enabled": true
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"id": "custom",
|
| 41 |
+
"label": "Custom Blocks",
|
| 42 |
+
"enabled": false
|
| 43 |
+
}
|
| 44 |
+
]
|
| 45 |
+
},
|
| 46 |
+
"editor": {
|
| 47 |
+
"autosave": true,
|
| 48 |
+
"storageType": "local",
|
| 49 |
+
"canvas": {
|
| 50 |
+
"styles": [
|
| 51 |
+
"https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/css/bootstrap.min.css"
|
| 52 |
+
],
|
| 53 |
+
"scripts": [
|
| 54 |
+
"https://code.jquery.com/jquery-3.5.1.min.js"
|
| 55 |
+
]
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
}
|
exports/.gitkeep
ADDED
|
File without changes
|
index.html
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="ja">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>API Workflow Builder - Visual Page Editor</title>
|
| 7 |
+
|
| 8 |
+
<!-- GrapeJS CSS -->
|
| 9 |
+
<link rel="stylesheet" href="https://unpkg.com/grapesjs/dist/css/grapes.min.css">
|
| 10 |
+
<link rel="stylesheet" href="https://unpkg.com/grapesjs-blocks-basic/dist/grapesjs-blocks-basic.min.css">
|
| 11 |
+
|
| 12 |
+
<style>
|
| 13 |
+
body, html {
|
| 14 |
+
height: 100%;
|
| 15 |
+
margin: 0;
|
| 16 |
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
#header {
|
| 20 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 21 |
+
color: white;
|
| 22 |
+
padding: 15px 30px;
|
| 23 |
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
| 24 |
+
display: flex;
|
| 25 |
+
justify-content: space-between;
|
| 26 |
+
align-items: center;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
#header h1 {
|
| 30 |
+
margin: 0;
|
| 31 |
+
font-size: 24px;
|
| 32 |
+
font-weight: 600;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
#header-buttons {
|
| 36 |
+
display: flex;
|
| 37 |
+
gap: 10px;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
#header button {
|
| 41 |
+
padding: 10px 20px;
|
| 42 |
+
border: none;
|
| 43 |
+
border-radius: 6px;
|
| 44 |
+
cursor: pointer;
|
| 45 |
+
font-size: 14px;
|
| 46 |
+
font-weight: 500;
|
| 47 |
+
transition: all 0.3s;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
.btn-preview {
|
| 51 |
+
background: #ffffff;
|
| 52 |
+
color: #667eea;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
.btn-save {
|
| 56 |
+
background: #48bb78;
|
| 57 |
+
color: white;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
.btn-load {
|
| 61 |
+
background: #4299e1;
|
| 62 |
+
color: white;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
.btn-clear {
|
| 66 |
+
background: #f56565;
|
| 67 |
+
color: white;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
#header button:hover {
|
| 71 |
+
transform: translateY(-2px);
|
| 72 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
#gjs {
|
| 76 |
+
height: calc(100vh - 70px);
|
| 77 |
+
overflow: hidden;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
/* Shop11 API Block カスタムスタイル */
|
| 81 |
+
.shop11-api-block {
|
| 82 |
+
padding: 20px;
|
| 83 |
+
margin: 15px 0;
|
| 84 |
+
border: 2px solid #e2e8f0;
|
| 85 |
+
border-radius: 8px;
|
| 86 |
+
background: #f7fafc;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.shop11-api-block h3 {
|
| 90 |
+
margin-top: 0;
|
| 91 |
+
color: #2d3748;
|
| 92 |
+
border-bottom: 2px solid #667eea;
|
| 93 |
+
padding-bottom: 10px;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
.api-button {
|
| 97 |
+
background: #667eea;
|
| 98 |
+
color: white;
|
| 99 |
+
border: none;
|
| 100 |
+
padding: 10px 20px;
|
| 101 |
+
border-radius: 5px;
|
| 102 |
+
cursor: pointer;
|
| 103 |
+
font-weight: 500;
|
| 104 |
+
transition: background 0.3s;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
.api-button:hover {
|
| 108 |
+
background: #5a67d8;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.api-result {
|
| 112 |
+
margin-top: 15px;
|
| 113 |
+
padding: 15px;
|
| 114 |
+
background: white;
|
| 115 |
+
border-radius: 5px;
|
| 116 |
+
border: 1px solid #e2e8f0;
|
| 117 |
+
max-height: 300px;
|
| 118 |
+
overflow-y: auto;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
/* モーダルスタイル */
|
| 122 |
+
.modal {
|
| 123 |
+
display: none;
|
| 124 |
+
position: fixed;
|
| 125 |
+
z-index: 9999;
|
| 126 |
+
left: 0;
|
| 127 |
+
top: 0;
|
| 128 |
+
width: 100%;
|
| 129 |
+
height: 100%;
|
| 130 |
+
background-color: rgba(0,0,0,0.5);
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
.modal-content {
|
| 134 |
+
background-color: #fefefe;
|
| 135 |
+
margin: 5% auto;
|
| 136 |
+
padding: 30px;
|
| 137 |
+
border-radius: 10px;
|
| 138 |
+
width: 80%;
|
| 139 |
+
max-width: 600px;
|
| 140 |
+
max-height: 80vh;
|
| 141 |
+
overflow-y: auto;
|
| 142 |
+
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
.close {
|
| 146 |
+
color: #aaa;
|
| 147 |
+
float: right;
|
| 148 |
+
font-size: 28px;
|
| 149 |
+
font-weight: bold;
|
| 150 |
+
cursor: pointer;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
.close:hover {
|
| 154 |
+
color: #000;
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
.page-item {
|
| 158 |
+
padding: 15px;
|
| 159 |
+
margin: 10px 0;
|
| 160 |
+
background: #f7fafc;
|
| 161 |
+
border-radius: 5px;
|
| 162 |
+
border: 1px solid #e2e8f0;
|
| 163 |
+
cursor: pointer;
|
| 164 |
+
transition: all 0.3s;
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
.page-item:hover {
|
| 168 |
+
background: #edf2f7;
|
| 169 |
+
border-color: #667eea;
|
| 170 |
+
}
|
| 171 |
+
</style>
|
| 172 |
+
</head>
|
| 173 |
+
<body>
|
| 174 |
+
<div id="header">
|
| 175 |
+
<h1>🚀 API Workflow Builder</h1>
|
| 176 |
+
<div id="header-buttons">
|
| 177 |
+
<button class="btn-preview" onclick="previewPage()">👁️ プレビュー</button>
|
| 178 |
+
<button class="btn-load" onclick="loadPageList()">📂 読込</button>
|
| 179 |
+
<button class="btn-save" onclick="savePage()">💾 保存</button>
|
| 180 |
+
<button class="btn-clear" onclick="clearPage()">🗑️ クリア</button>
|
| 181 |
+
</div>
|
| 182 |
+
</div>
|
| 183 |
+
|
| 184 |
+
<div id="gjs"></div>
|
| 185 |
+
|
| 186 |
+
<!-- ページ一覧モーダル -->
|
| 187 |
+
<div id="pageListModal" class="modal">
|
| 188 |
+
<div class="modal-content">
|
| 189 |
+
<span class="close" onclick="closeModal()">×</span>
|
| 190 |
+
<h2>📂 保存されたページ</h2>
|
| 191 |
+
<div id="pageList"></div>
|
| 192 |
+
</div>
|
| 193 |
+
</div>
|
| 194 |
+
|
| 195 |
+
<!-- Supabase JS SDK -->
|
| 196 |
+
<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
|
| 197 |
+
|
| 198 |
+
<!-- GrapeJS -->
|
| 199 |
+
<script src="https://unpkg.com/grapesjs"></script>
|
| 200 |
+
<script src="https://unpkg.com/grapesjs-blocks-basic"></script>
|
| 201 |
+
|
| 202 |
+
<!-- Supabase Config -->
|
| 203 |
+
<script src="/shop11/public/page-builder/supabase-config.js"></script>
|
| 204 |
+
|
| 205 |
+
<script>
|
| 206 |
+
// GrapeJS エディタ初期化
|
| 207 |
+
const editor = grapesjs.init({
|
| 208 |
+
container: '#gjs',
|
| 209 |
+
fromElement: false,
|
| 210 |
+
height: '100%',
|
| 211 |
+
width: 'auto',
|
| 212 |
+
plugins: ['gjs-blocks-basic'],
|
| 213 |
+
storageManager: {
|
| 214 |
+
type: 'local',
|
| 215 |
+
autosave: true,
|
| 216 |
+
autoload: true,
|
| 217 |
+
stepsBeforeSave: 1
|
| 218 |
+
},
|
| 219 |
+
canvas: {
|
| 220 |
+
styles: [
|
| 221 |
+
'https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/css/bootstrap.min.css'
|
| 222 |
+
],
|
| 223 |
+
scripts: [
|
| 224 |
+
'https://code.jquery.com/jquery-3.5.1.min.js',
|
| 225 |
+
'/shop11/public/page-builder/api-functions.js'
|
| 226 |
+
]
|
| 227 |
+
}
|
| 228 |
+
});
|
| 229 |
+
|
| 230 |
+
// カスタムブロック登録
|
| 231 |
+
editor.on('load', function() {
|
| 232 |
+
const blockManager = editor.BlockManager;
|
| 233 |
+
|
| 234 |
+
// 地金チェック削除ブロック
|
| 235 |
+
blockManager.add('gold-check-delete', {
|
| 236 |
+
label: '地金チェック削除',
|
| 237 |
+
category: 'Shop11 API',
|
| 238 |
+
content: '<div class="shop11-api-block" data-api="gold_check"><h3>🥇 地金チェック削除</h3><div class="form-group"><label>商品ID:</label><input type="text" class="form-control gold-product-id" placeholder="商品IDを入力"></div><button class="btn btn-danger" data-action="goldCheckDelete">削除実行</button><div class="api-result gold-check-result" style="display:none;"></div></div>',
|
| 239 |
+
attributes: { class: 'fa fa-trash' }
|
| 240 |
+
});
|
| 241 |
+
|
| 242 |
+
// 査定タイトル生成ブロック
|
| 243 |
+
blockManager.add('satei-title', {
|
| 244 |
+
label: '査定タイトル生成',
|
| 245 |
+
category: 'Shop11 API',
|
| 246 |
+
content: '<div class="shop11-api-block" data-api="satei_title"><h3>📝 査定タイトル生成</h3><div class="form-group"><label>商品ID:</label><input type="text" class="form-control satei-product-id" placeholder="商品IDを入力"></div><button class="btn btn-primary" data-action="createSateiTitle">タイトル生成</button><div class="api-result satei-title-result" style="display:none;"></div></div>',
|
| 247 |
+
attributes: { class: 'fa fa-file-text' }
|
| 248 |
+
});
|
| 249 |
+
|
| 250 |
+
// メール送信ブロック
|
| 251 |
+
blockManager.add('send-notification', {
|
| 252 |
+
label: 'メール送信',
|
| 253 |
+
category: 'Shop11 API',
|
| 254 |
+
content: '<div class="shop11-api-block" data-api="notification"><h3>📧 メール送信</h3><div class="form-group"><label>宛先:</label><input type="email" class="form-control mail-to" placeholder="email@example.com"></div><div class="form-group"><label>件名:</label><input type="text" class="form-control mail-subject" placeholder="件名を入力"></div><div class="form-group"><label>本文:</label><textarea class="form-control mail-body" rows="4" placeholder="メッセージを入力"></textarea></div><button class="btn btn-success" data-action="sendNotification">送信</button><div class="api-result mail-result" style="display:none;"></div></div>',
|
| 255 |
+
attributes: { class: 'fa fa-envelope' }
|
| 256 |
+
});
|
| 257 |
+
|
| 258 |
+
// 商品検索ブロック
|
| 259 |
+
blockManager.add('product-search', {
|
| 260 |
+
label: '商品検索',
|
| 261 |
+
category: 'Shop11 API',
|
| 262 |
+
content: '<div class="shop11-api-block" data-api="product_search"><h3>🔍 商品検索</h3><div class="form-group"><label>検索キーワード:</label><input type="text" class="form-control search-keyword" placeholder="商品名・ブランドで検索"></div><button class="btn btn-info" data-action="searchProduct">検索</button><div class="api-result search-result" style="display:none;"></div></div>',
|
| 263 |
+
attributes: { class: 'fa fa-search' }
|
| 264 |
+
});
|
| 265 |
+
|
| 266 |
+
// データテーブルブロック
|
| 267 |
+
blockManager.add('data-table', {
|
| 268 |
+
label: 'データテーブル',
|
| 269 |
+
category: 'Shop11 UI',
|
| 270 |
+
content: '<div class="container my-4"><h3>データ一覧</h3><table class="table table-striped table-bordered"><thead class="thead-dark"><tr><th>ID</th><th>商品名</th><th>ステータス</th><th>金額</th><th>操作</th></tr></thead><tbody><tr><td>1</td><td>サンプル商品A</td><td><span class="badge badge-success">完了</span></td><td>¥10,000</td><td><button class="btn btn-sm btn-primary">編集</button></td></tr><tr><td>2</td><td>サンプル商品B</td><td><span class="badge badge-warning">処理中</span></td><td>¥25,000</td><td><button class="btn btn-sm btn-primary">編集</button></td></tr></tbody></table></div>',
|
| 271 |
+
attributes: { class: 'fa fa-table' }
|
| 272 |
+
});
|
| 273 |
+
|
| 274 |
+
// 商品データテーブルブロック
|
| 275 |
+
blockManager.add('product-data-table', {
|
| 276 |
+
label: '商品データテーブル',
|
| 277 |
+
category: 'Shop11 API',
|
| 278 |
+
content: '<div class="shop11-api-block" data-api="product_table"><h3>商品データテーブル</h3><div class="form-group"><label>検索キーワード:</label><div class="input-group"><input type="text" class="form-control product-search-keyword" placeholder="商品名・ブランドで検索"><div class="input-group-append"><button class="btn btn-primary" data-action="loadProductTable">🔍 検索</button><button class="btn btn-secondary ml-2" data-action="clearProductTable">🗑️ クリア</button></div></div></div><div class="product-table-container"><table class="table table-striped table-bordered table-hover"><thead class="thead-dark"><tr><th>商品ID</th><th>商品名</th><th>ブランド</th><th>カテゴリ</th><th>価格</th><th>ステータス</th><th>操作</th></tr></thead><tbody class="product-table-body"><tr><td colspan="7" class="text-center text-muted">検索キーワードを入力して検索してください</td></tr></tbody></table></div><div class="product-table-info mt-2 text-muted"></div></div>',
|
| 279 |
+
attributes: { class: 'fa fa-database' }
|
| 280 |
+
});
|
| 281 |
+
|
| 282 |
+
// 顧客検索テーブルブロック
|
| 283 |
+
blockManager.add('customer-data-table', {
|
| 284 |
+
label: '顧客検索テーブル',
|
| 285 |
+
category: 'Shop11 API',
|
| 286 |
+
content: '<div class="shop11-api-block" data-api="customer_table"><h3>👤 顧客検索テーブル</h3><div class="form-group"><label>顧客名・電話番号:</label><div class="input-group"><input type="text" class="form-control customer-search-keyword" placeholder="顧客名・電話番号で検索"><div class="input-group-append"><button class="btn btn-primary" data-action="loadCustomerTable">🔍 検索</button><button class="btn btn-secondary ml-2" data-action="clearCustomerTable">🗑️ クリア</button></div></div></div><div class="customer-table-container"><table class="table table-striped table-bordered table-hover"><thead class="thead-dark"><tr><th>顧客ID</th><th>顧客名</th><th>電話番号</th><th>メール</th><th>郵便番号</th><th>住所</th><th>操作</th></tr></thead><tbody class="customer-table-body"><tr><td colspan="7" class="text-center text-muted">検索キーワードを入力して検索してください</td></tr></tbody></table></div><div class="customer-table-info mt-2 text-muted"></div></div>',
|
| 287 |
+
attributes: { class: 'fa fa-users' }
|
| 288 |
+
});
|
| 289 |
+
|
| 290 |
+
// 査定検索テーブルブロック
|
| 291 |
+
blockManager.add('satei-data-table', {
|
| 292 |
+
label: '査定検索テーブル',
|
| 293 |
+
category: 'Shop11 API',
|
| 294 |
+
content: '<div class="shop11-api-block" data-api="satei_table"><h3>📝 査定検索テーブル</h3><div class="form-group"><label>検索条件:</label><div class="input-group"><input type="text" class="form-control satei-search-keyword" placeholder="商品名・顧客名で検索"><div class="input-group-append"><button class="btn btn-primary" data-action="loadSateiTable">🔍 検索</button><button class="btn btn-secondary ml-2" data-action="clearSateiTable">🗑️ クリア</button></div></div></div><div class="satei-table-container"><table class="table table-striped table-bordered table-hover"><thead class="thead-dark"><tr><th>査定ID</th><th>商品名</th><th>顧客名</th><th>査定額</th><th>査定日</th><th>ステータス</th><th>操作</th></tr></thead><tbody class="satei-table-body"><tr><td colspan="7" class="text-center text-muted">検索キーワードを入力して検索してください</td></tr></tbody></table></div><div class="satei-table-info mt-2 text-muted"></div></div>',
|
| 295 |
+
attributes: { class: 'fa fa-clipboard' }
|
| 296 |
+
});
|
| 297 |
+
|
| 298 |
+
// 商品登録フォームブロック
|
| 299 |
+
blockManager.add('product-register-form', {
|
| 300 |
+
label: '商品登録フォーム',
|
| 301 |
+
category: 'Shop11 API',
|
| 302 |
+
content: '<div class="shop11-api-block" data-api="product_register"><h3>✏️ 商品登録フォーム</h3><form class="product-register-form"><div class="row"><div class="col-md-6"><div class="form-group"><label>商品名 <span class="text-danger">*</span></label><input type="text" class="form-control product-name" placeholder="商品名を入力" required></div></div><div class="col-md-6"><div class="form-group"><label>ブランド</label><input type="text" class="form-control product-brand" placeholder="ブランド名"></div></div></div><div class="row"><div class="col-md-6"><div class="form-group"><label>カテゴリ</label><select class="form-control product-category"><option value="">選択してください</option><option value="1">ジュエリー</option><option value="2">時計</option><option value="3">バッグ</option><option value="4">貴金属</option><option value="5">その他</option></select></div></div><div class="col-md-6"><div class="form-group"><label>価格 <span class="text-danger">*</span></label><input type="number" class="form-control product-price" placeholder="価格を入力" required></div></div></div><div class="form-group"><label>商品説明</label><textarea class="form-control product-description" rows="3" placeholder="商品の説明を入力"></textarea></div><div class="form-group"><label>ステータス</label><select class="form-control product-status"><option value="1" selected>在庫あり</option><option value="2">予約中</option><option value="3">売約済</option><option value="4">出品中</option></select></div><div class="text-right"><button type="button" class="btn btn-success btn-lg" data-action="registerProduct">💾 登録する</button><button type="reset" class="btn btn-secondary btn-lg ml-2">🔄 リセット</button></div></form><div class="register-result mt-3" style="display:none;"></div></div>',
|
| 303 |
+
attributes: { class: 'fa fa-plus-square' }
|
| 304 |
+
});
|
| 305 |
+
|
| 306 |
+
console.log('✅ API Workflow Builder loaded!');
|
| 307 |
+
console.log('📦 Total Blocks:', blockManager.getAll().length);
|
| 308 |
+
});
|
| 309 |
+
|
| 310 |
+
// プレビュー機能
|
| 311 |
+
function previewPage() {
|
| 312 |
+
const html = editor.getHtml();
|
| 313 |
+
const css = '<style>' + editor.getCss() + '</style>';
|
| 314 |
+
const previewWindow = window.open('', '_blank');
|
| 315 |
+
previewWindow.document.write(`
|
| 316 |
+
<!DOCTYPE html>
|
| 317 |
+
<html>
|
| 318 |
+
<head>
|
| 319 |
+
<meta charset="UTF-8">
|
| 320 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 321 |
+
<title>Preview</title>
|
| 322 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/css/bootstrap.min.css">
|
| 323 |
+
${css}
|
| 324 |
+
</head>
|
| 325 |
+
<body>
|
| 326 |
+
${html}
|
| 327 |
+
<script src="https://code.jquery.com/jquery-3.5.1.min.js"><\/script>
|
| 328 |
+
<script src="/shop11/public/page-builder/api-functions.js"><\/script>
|
| 329 |
+
</body>
|
| 330 |
+
</html>
|
| 331 |
+
`);
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
// 保存機能
|
| 335 |
+
function savePage() {
|
| 336 |
+
const html = editor.getHtml();
|
| 337 |
+
const css = editor.getCss();
|
| 338 |
+
const components = editor.getComponents();
|
| 339 |
+
|
| 340 |
+
const pageName = prompt('ページ名を入力してください:', 'page-' + Date.now());
|
| 341 |
+
if (!pageName) return;
|
| 342 |
+
|
| 343 |
+
savePageToSupabase(pageName, html, css, components).then(result => {
|
| 344 |
+
if (result.success) {
|
| 345 |
+
alert('✅ Supabaseに保存しました!\nページID: ' + result.data[0].id);
|
| 346 |
+
downloadHtmlFile(pageName, html, css);
|
| 347 |
+
} else {
|
| 348 |
+
alert('❌ 保存エラー: ' + result.error);
|
| 349 |
+
}
|
| 350 |
+
});
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
// HTMLファイルダウンロード
|
| 354 |
+
function downloadHtmlFile(pageName, html, css) {
|
| 355 |
+
const fullHtml = `<!DOCTYPE html>
|
| 356 |
+
<html lang="ja">
|
| 357 |
+
<head>
|
| 358 |
+
<meta charset="UTF-8">
|
| 359 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 360 |
+
<title>${pageName}</title>
|
| 361 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/css/bootstrap.min.css">
|
| 362 |
+
<style>${css}</style>
|
| 363 |
+
</head>
|
| 364 |
+
<body>
|
| 365 |
+
${html}
|
| 366 |
+
<script src="https://code.jquery.com/jquery-3.5.1.min.js"><\/script>
|
| 367 |
+
<script src="/shop11/public/page-builder/api-functions.js"><\/script>
|
| 368 |
+
</body>
|
| 369 |
+
</html>`;
|
| 370 |
+
|
| 371 |
+
const blob = new Blob([fullHtml], { type: 'text/html' });
|
| 372 |
+
const url = URL.createObjectURL(blob);
|
| 373 |
+
const a = document.createElement('a');
|
| 374 |
+
a.href = url;
|
| 375 |
+
a.download = pageName + '.html';
|
| 376 |
+
a.click();
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
// ページ一覧読込
|
| 380 |
+
function loadPageList() {
|
| 381 |
+
loadPagesFromSupabase().then(result => {
|
| 382 |
+
if (result.success && result.data.length > 0) {
|
| 383 |
+
const pageListDiv = document.getElementById('pageList');
|
| 384 |
+
pageListDiv.innerHTML = '';
|
| 385 |
+
|
| 386 |
+
result.data.forEach(page => {
|
| 387 |
+
const pageItem = document.createElement('div');
|
| 388 |
+
pageItem.className = 'page-item';
|
| 389 |
+
pageItem.innerHTML = `
|
| 390 |
+
<strong>${page.name}</strong><br>
|
| 391 |
+
<small>作成日: ${new Date(page.created_at).toLocaleString('ja-JP')}</small>
|
| 392 |
+
`;
|
| 393 |
+
pageItem.onclick = () => loadPageFromSupabaseById(page.id);
|
| 394 |
+
pageListDiv.appendChild(pageItem);
|
| 395 |
+
});
|
| 396 |
+
|
| 397 |
+
document.getElementById('pageListModal').style.display = 'block';
|
| 398 |
+
} else {
|
| 399 |
+
alert('保存されたページがありません');
|
| 400 |
+
}
|
| 401 |
+
});
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
// ページ読込
|
| 405 |
+
function loadPageFromSupabaseById(pageId) {
|
| 406 |
+
loadPageFromSupabase(pageId).then(result => {
|
| 407 |
+
if (result.success && result.data) {
|
| 408 |
+
const page = result.data;
|
| 409 |
+
editor.setComponents(page.html_content);
|
| 410 |
+
editor.setStyle(page.css_content);
|
| 411 |
+
closeModal();
|
| 412 |
+
alert('✅ ページを読み込みました: ' + page.name);
|
| 413 |
+
} else {
|
| 414 |
+
alert('❌ 読込エラー: ' + result.error);
|
| 415 |
+
}
|
| 416 |
+
});
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
// モーダルを閉じる
|
| 420 |
+
function closeModal() {
|
| 421 |
+
document.getElementById('pageListModal').style.display = 'none';
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
// クリア機能
|
| 425 |
+
function clearPage() {
|
| 426 |
+
if (confirm('すべてのコンテンツをクリアしますか?')) {
|
| 427 |
+
editor.setComponents('');
|
| 428 |
+
editor.setStyle('');
|
| 429 |
+
alert('✅ クリアしました');
|
| 430 |
+
}
|
| 431 |
+
}
|
| 432 |
+
|
| 433 |
+
// モーダル外クリックで閉じる
|
| 434 |
+
window.onclick = function(event) {
|
| 435 |
+
const modal = document.getElementById('pageListModal');
|
| 436 |
+
if (event.target == modal) {
|
| 437 |
+
closeModal();
|
| 438 |
+
}
|
| 439 |
+
}
|
| 440 |
+
</script>
|
| 441 |
+
</body>
|
| 442 |
+
</html>
|
requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
gradio>=4.0.0
|
supabase-config.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Supabase Configuration and API Functions
|
| 2 |
+
// API Workflow Builder - Database Integration
|
| 3 |
+
|
| 4 |
+
const SUPABASE_URL = 'https://rootomzbucovwdqsscqd.supabase.co';
|
| 5 |
+
const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJvb3RvbXpidWNvdndkcXNzY3FkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzU4OTE4ODMsImV4cCI6MjA1MTQ2Nzg4M30.fYKOe-HPh4WUdvBhEJxakLWCMQBp4E90EDwARk7ucf8';
|
| 6 |
+
|
| 7 |
+
// Supabaseクライアント初期化
|
| 8 |
+
const supabaseClient = supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
|
| 9 |
+
|
| 10 |
+
console.log('✅ Supabase Client initialized');
|
| 11 |
+
|
| 12 |
+
/**
|
| 13 |
+
* ページをSupabaseに保存
|
| 14 |
+
* @param {string} pageName - ページ名
|
| 15 |
+
* @param {string} html - HTML内容
|
| 16 |
+
* @param {string} css - CSS内容
|
| 17 |
+
* @param {object} components - GrapeJSコンポーネント
|
| 18 |
+
* @returns {Promise<{success: boolean, data: any, error: string}>}
|
| 19 |
+
*/
|
| 20 |
+
async function savePageToSupabase(pageName, html, css, components) {
|
| 21 |
+
try {
|
| 22 |
+
const { data, error } = await supabaseClient
|
| 23 |
+
.from('page_builder_pages')
|
| 24 |
+
.insert({
|
| 25 |
+
name: pageName,
|
| 26 |
+
html_content: html,
|
| 27 |
+
css_content: css,
|
| 28 |
+
components_json: components,
|
| 29 |
+
created_at: new Date().toISOString(),
|
| 30 |
+
updated_at: new Date().toISOString()
|
| 31 |
+
})
|
| 32 |
+
.select();
|
| 33 |
+
|
| 34 |
+
if (error) throw error;
|
| 35 |
+
|
| 36 |
+
console.log('✅ Page saved to Supabase:', data);
|
| 37 |
+
return { success: true, data: data, error: null };
|
| 38 |
+
} catch (error) {
|
| 39 |
+
console.error('❌ Error saving page:', error);
|
| 40 |
+
return { success: false, data: null, error: error.message };
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
/**
|
| 45 |
+
* Supabaseから全ページを取得
|
| 46 |
+
* @returns {Promise<{success: boolean, data: any[], error: string}>}
|
| 47 |
+
*/
|
| 48 |
+
async function loadPagesFromSupabase() {
|
| 49 |
+
try {
|
| 50 |
+
const { data, error } = await supabaseClient
|
| 51 |
+
.from('page_builder_pages')
|
| 52 |
+
.select('*')
|
| 53 |
+
.order('updated_at', { ascending: false });
|
| 54 |
+
|
| 55 |
+
if (error) throw error;
|
| 56 |
+
|
| 57 |
+
console.log('✅ Pages loaded from Supabase:', data.length);
|
| 58 |
+
return { success: true, data: data, error: null };
|
| 59 |
+
} catch (error) {
|
| 60 |
+
console.error('❌ Error loading pages:', error);
|
| 61 |
+
return { success: false, data: [], error: error.message };
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
/**
|
| 66 |
+
* Supabaseから特定のページを取得
|
| 67 |
+
* @param {string} pageId - ページID
|
| 68 |
+
* @returns {Promise<{success: boolean, data: any, error: string}>}
|
| 69 |
+
*/
|
| 70 |
+
async function loadPageFromSupabase(pageId) {
|
| 71 |
+
try {
|
| 72 |
+
const { data, error } = await supabaseClient
|
| 73 |
+
.from('page_builder_pages')
|
| 74 |
+
.select('*')
|
| 75 |
+
.eq('id', pageId)
|
| 76 |
+
.single();
|
| 77 |
+
|
| 78 |
+
if (error) throw error;
|
| 79 |
+
|
| 80 |
+
console.log('✅ Page loaded from Supabase:', data.name);
|
| 81 |
+
return { success: true, data: data, error: null };
|
| 82 |
+
} catch (error) {
|
| 83 |
+
console.error('❌ Error loading page:', error);
|
| 84 |
+
return { success: false, data: null, error: error.message };
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
/**
|
| 89 |
+
* API実行ログをSupabaseに保存
|
| 90 |
+
* @param {string} apiName - API名
|
| 91 |
+
* @param {string} endpoint - エンドポイント
|
| 92 |
+
* @param {object} request - リクエストデータ
|
| 93 |
+
* @param {object} response - レスポンスデータ
|
| 94 |
+
* @param {string} status - ステータス ('success' or 'error')
|
| 95 |
+
* @returns {Promise<{success: boolean, data: any, error: string}>}
|
| 96 |
+
*/
|
| 97 |
+
async function saveApiLogToSupabase(apiName, endpoint, request, response, status) {
|
| 98 |
+
try {
|
| 99 |
+
const { data, error } = await supabaseClient
|
| 100 |
+
.from('api_execution_logs')
|
| 101 |
+
.insert({
|
| 102 |
+
api_name: apiName,
|
| 103 |
+
endpoint: endpoint,
|
| 104 |
+
request_data: request,
|
| 105 |
+
response_data: response,
|
| 106 |
+
status: status,
|
| 107 |
+
executed_at: new Date().toISOString()
|
| 108 |
+
})
|
| 109 |
+
.select();
|
| 110 |
+
|
| 111 |
+
if (error) throw error;
|
| 112 |
+
|
| 113 |
+
console.log('✅ API log saved:', apiName, status);
|
| 114 |
+
return { success: true, data: data, error: null };
|
| 115 |
+
} catch (error) {
|
| 116 |
+
console.error('❌ Error saving API log:', error);
|
| 117 |
+
return { success: false, data: null, error: error.message };
|
| 118 |
+
}
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
/**
|
| 122 |
+
* Supabaseから API実行ログを取得
|
| 123 |
+
* @param {number} limit - 取得件数
|
| 124 |
+
* @returns {Promise<{success: boolean, data: any[], error: string}>}
|
| 125 |
+
*/
|
| 126 |
+
async function getApiLogsFromSupabase(limit = 50) {
|
| 127 |
+
try {
|
| 128 |
+
const { data, error } = await supabaseClient
|
| 129 |
+
.from('api_execution_logs')
|
| 130 |
+
.select('*')
|
| 131 |
+
.order('executed_at', { ascending: false })
|
| 132 |
+
.limit(limit);
|
| 133 |
+
|
| 134 |
+
if (error) throw error;
|
| 135 |
+
|
| 136 |
+
console.log('✅ API logs loaded:', data.length);
|
| 137 |
+
return { success: true, data: data, error: null };
|
| 138 |
+
} catch (error) {
|
| 139 |
+
console.error('❌ Error loading API logs:', error);
|
| 140 |
+
return { success: false, data: [], error: error.message };
|
| 141 |
+
}
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
// グローバルに公開
|
| 145 |
+
window.savePageToSupabase = savePageToSupabase;
|
| 146 |
+
window.loadPagesFromSupabase = loadPagesFromSupabase;
|
| 147 |
+
window.loadPageFromSupabase = loadPageFromSupabase;
|
| 148 |
+
window.saveApiLogToSupabase = saveApiLogToSupabase;
|
| 149 |
+
window.getApiLogsFromSupabase = getApiLogsFromSupabase;
|
workflows/.gitkeep
ADDED
|
File without changes
|