diff --git a/.agents/skills/orchestrator/SKILL.md b/.agents/skills/orchestrator/SKILL.md
index a769f84..26452da 100644
--- a/.agents/skills/orchestrator/SKILL.md
+++ b/.agents/skills/orchestrator/SKILL.md
@@ -16,7 +16,7 @@ description: Main Agent / Multi-Agent Orchestrator for planning, delegation, coo
## Operating Principles
- **Clarify before committing:** ถ้า requirement เสี่ยงหรือไม่ชัด ให้ค้นจาก repo/task/context ก่อน ถ้ายังไม่พอจึงถาม User
-- **Delegate by default:** งานเฉพาะทางควรส่งให้ specialist agent ที่เหมาะสม ยกเว้นงานเล็กมากหรือ User สั่งให้ทำเองชัดเจน
+- **Delegate by default:** งานเฉพาะทางต้องส่งให้ specialist agent ที่เหมาะสมทำเสมอ หาก User สั่งว่า "ทำได้เลย" หรือ "ลงมือได้เลย" หมายถึงการสั่งให้คุณส่งงานต่อให้ Sub Agent ที่เหมาะสมลงมือทำ
- **Read before assigning:** ก่อนสั่งงานโดยอ้างชื่อ field, type, API, schema, route, token, หรือ component ต้องอ่าน source จริงก่อน
- **Verify after delivery:** งานจาก agent ต้องถูกตรวจตาม risk ไม่ใช่รับผลแล้วส่งต่อทันที
- **Escalate early:** ถ้าปัญหาข้ามขอบเขต agent หรือกระทบ contract/schema/infra ให้หยุดและแจ้งผู้รับผิดชอบ
@@ -66,7 +66,7 @@ description: Main Agent / Multi-Agent Orchestrator for planning, delegation, coo
> [!IMPORTANT]
> **RULE #2: ใช้ specialist agent ให้ถูกงาน**
-> อย่าทำงานเฉพาะทางเองถ้ามี agent ที่รับผิดชอบชัดเจน ยกเว้นงานเล็กมาก, ไม่มีเครื่องมือ delegate พร้อมใช้, หรือ User สั่งให้ลงมือเอง
+> อย่าทำงานเฉพาะทางเองเด็ดขาด ต้องมอบหมายให้ agent ที่รับผิดชอบชัดเจนเสมอ หาก User สั่งว่า "ทำได้เลย" หรือ "ลงมือได้เลย" หมายถึงคำสั่งให้คุณเรียกใช้งาน Sub Agent ที่มี Skill เหมาะสมเพื่อลงมือทำ
> [!WARNING]
> **RULE #3: ห้าม bypass process หรือ tool**
diff --git a/apps/admin/src/App.tsx b/apps/admin/src/App.tsx
index 787359a..4661134 100644
--- a/apps/admin/src/App.tsx
+++ b/apps/admin/src/App.tsx
@@ -46,6 +46,7 @@ function App() {
}>
} />
} />
+ } />
} />
} />
} />
diff --git a/apps/admin/src/pages/OrderDetailPage.tsx b/apps/admin/src/pages/OrderDetailPage.tsx
index b752df9..e8defc1 100644
--- a/apps/admin/src/pages/OrderDetailPage.tsx
+++ b/apps/admin/src/pages/OrderDetailPage.tsx
@@ -86,7 +86,7 @@ export default function OrderDetailPage() {
{formatPrice(item.priceAtPurchase)}
diff --git a/apps/admin/src/pages/ProductEditPage.tsx b/apps/admin/src/pages/ProductEditPage.tsx
index 06c0891..c1b1007 100644
--- a/apps/admin/src/pages/ProductEditPage.tsx
+++ b/apps/admin/src/pages/ProductEditPage.tsx
@@ -88,29 +88,37 @@ export default function ProductEditPage() {
useEffect(() => {
const fetchData = async () => {
try {
- const [prodRes, catRes] = await Promise.all([
- fetch(`${API_BASE}/products/${id}?lang=all`),
- fetch(`${API_BASE}/categories`)
- ])
-
- if (!prodRes.ok) throw new Error('Product not found')
-
- const prodData: Product = await prodRes.json()
+ const catRes = await fetch(`${API_BASE}/categories`)
const catData: Category[] = await catRes.json()
-
- setProduct(prodData)
setCategories(catData)
- setCategoryId(prodData.categoryId)
-
- const parseTranslatable = (val: any) => typeof val === 'object' && val !== null ? val : { th: val || '' }
- setName(parseTranslatable(prodData.name))
- setDescription(parseTranslatable(prodData.description))
- setDetails(parseTranslatable(prodData.details))
-
- const parseOptions = (val: any) => Array.isArray(val) ? val : []
- setOptions(parseOptions(prodData.options))
- setVariants(prodData.variants || [])
- setImages(prodData.images || [])
+
+ if (id) {
+ const prodRes = await fetch(`${API_BASE}/products/${id}?lang=all`)
+ if (!prodRes.ok) throw new Error('Product not found')
+
+ const prodData: Product = await prodRes.json()
+ setProduct(prodData)
+ setCategoryId(prodData.categoryId)
+
+ const parseTranslatable = (val: any) => typeof val === 'object' && val !== null ? val : { th: val || '' }
+ setName(parseTranslatable(prodData.name))
+ setDescription(parseTranslatable(prodData.description))
+ setDetails(parseTranslatable(prodData.details))
+
+ const parseOptions = (val: any) => Array.isArray(val) ? val : []
+ setOptions(parseOptions(prodData.options))
+ setVariants(prodData.variants || [])
+ setImages(prodData.images || [])
+ } else {
+ setProduct(null)
+ if (catData.length > 0) setCategoryId(catData[0].id)
+ setName({ th: '', en: '' })
+ setDescription({ th: '', en: '' })
+ setDetails({ th: '', en: '' })
+ setOptions([])
+ setVariants([])
+ setImages([])
+ }
} catch (err: any) {
setError(err.message)
} finally {
@@ -172,8 +180,12 @@ export default function ProductEditPage() {
setSuccess(false)
try {
- const response = await fetch(`${API_BASE}/products/${id}`, {
- method: 'PATCH',
+ const isNew = !id
+ const method = isNew ? 'POST' : 'PUT'
+ const url = isNew ? `${API_BASE}/products` : `${API_BASE}/products/${id}`
+
+ const response = await fetch(url, {
+ method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
categoryId,
@@ -182,6 +194,7 @@ export default function ProductEditPage() {
details,
options,
variants: variants.map(v => ({
+ ...(v.id ? { id: v.id } : {}),
option1: v.option1 || '',
option2: v.option2 || '',
option3: v.option3 || '',
@@ -191,16 +204,21 @@ export default function ProductEditPage() {
barcode: v.barcode || ''
})),
images: images.filter(img => img.url).map((img, idx) => ({
+ ...(img.id ? { id: img.id } : {}),
url: img.url,
- order: img.order ?? idx
+ order: img.order ?? (typeof img.order === 'number' ? img.order : idx)
}))
}),
})
- if (!response.ok) throw new Error('Failed to update product')
+ if (!response.ok) throw new Error(isNew ? 'Failed to create product' : 'Failed to update product')
setSuccess(true)
- setTimeout(() => setSuccess(false), 3000)
+ if (isNew) {
+ setTimeout(() => navigate('/products'), 1500)
+ } else {
+ setTimeout(() => setSuccess(false), 3000)
+ }
} catch (err: any) {
setError(err.message)
} finally {
diff --git a/apps/admin/src/pages/ProductsPage.tsx b/apps/admin/src/pages/ProductsPage.tsx
index 50c28d7..b7652db 100644
--- a/apps/admin/src/pages/ProductsPage.tsx
+++ b/apps/admin/src/pages/ProductsPage.tsx
@@ -81,6 +81,7 @@ export default function ProductsPage() {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
categoryId: selectedProduct.categoryId,
+ options: selectedProduct.options,
variants: selectedProduct.variants.map(v => ({ option1: v.option1, option2: v.option2, option3: v.option3, sku: v.sku, barcode: v.barcode, stock: v.stock, price: v.price })),
images: updatedImages
})
@@ -145,6 +146,7 @@ export default function ProductsPage() {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
categoryId: selectedProduct.categoryId,
+ options: selectedProduct.options,
variants: selectedProduct.variants.map(v => ({ option1: v.option1, option2: v.option2, option3: v.option3, sku: v.sku, barcode: v.barcode, stock: v.stock, price: v.price })),
images: updatedImages
})
@@ -234,7 +236,10 @@ export default function ProductsPage() {
{meta ? `Displaying ${products.length} of ${meta.total} products` : 'Loading products...'}
-