@minimalcorp/meshi 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.next/standalone/apps/web/.next/BUILD_ID +1 -1
  2. package/.next/standalone/apps/web/.next/app-build-manifest.json +6 -6
  3. package/.next/standalone/apps/web/.next/app-path-routes-manifest.json +2 -2
  4. package/.next/standalone/apps/web/.next/build-manifest.json +2 -2
  5. package/.next/standalone/apps/web/.next/prerender-manifest.json +19 -19
  6. package/.next/standalone/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  7. package/.next/standalone/apps/web/.next/server/app/_not-found.html +1 -1
  8. package/.next/standalone/apps/web/.next/server/app/_not-found.rsc +1 -1
  9. package/.next/standalone/apps/web/.next/server/app/dashboard/page.js +1 -1
  10. package/.next/standalone/apps/web/.next/server/app/dashboard/page_client-reference-manifest.js +1 -1
  11. package/.next/standalone/apps/web/.next/server/app/dashboard.html +1 -1
  12. package/.next/standalone/apps/web/.next/server/app/dashboard.rsc +2 -2
  13. package/.next/standalone/apps/web/.next/server/app/foods/page.js +1 -1
  14. package/.next/standalone/apps/web/.next/server/app/foods/page_client-reference-manifest.js +1 -1
  15. package/.next/standalone/apps/web/.next/server/app/foods.html +1 -1
  16. package/.next/standalone/apps/web/.next/server/app/foods.rsc +2 -2
  17. package/.next/standalone/apps/web/.next/server/app/goals/page.js +1 -1
  18. package/.next/standalone/apps/web/.next/server/app/goals/page_client-reference-manifest.js +1 -1
  19. package/.next/standalone/apps/web/.next/server/app/goals.html +1 -1
  20. package/.next/standalone/apps/web/.next/server/app/goals.rsc +2 -2
  21. package/.next/standalone/apps/web/.next/server/app/index.html +1 -1
  22. package/.next/standalone/apps/web/.next/server/app/index.rsc +2 -2
  23. package/.next/standalone/apps/web/.next/server/app/page.js +1 -1
  24. package/.next/standalone/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  25. package/.next/standalone/apps/web/.next/server/app-paths-manifest.json +2 -2
  26. package/.next/standalone/apps/web/.next/server/pages/404.html +1 -1
  27. package/.next/standalone/apps/web/.next/server/pages/500.html +1 -1
  28. package/.next/standalone/apps/web/.next/server/server-reference-manifest.json +1 -1
  29. package/.next/standalone/apps/web/.next/static/chunks/app/dashboard/page-887b35a12f9529d2.js +1 -0
  30. package/.next/standalone/apps/web/.next/static/chunks/app/foods/page-180e9721acdbca25.js +1 -0
  31. package/.next/standalone/apps/web/.next/static/chunks/app/goals/page-33762df951ed61c0.js +1 -0
  32. package/.next/standalone/apps/web/.next/static/chunks/app/{page-2ddb4f0f2f148278.js → page-88c5d1480f5849fe.js} +1 -1
  33. package/.next/standalone/apps/web/package.json +2 -2
  34. package/dist/cli.js +13 -8
  35. package/dist/server/index.js +19 -3
  36. package/package.json +3 -3
  37. package/.next/standalone/apps/web/.next/static/chunks/app/dashboard/page-91507359fbc6638a.js +0 -1
  38. package/.next/standalone/apps/web/.next/static/chunks/app/foods/page-50858f76b2a6c8b8.js +0 -1
  39. package/.next/standalone/apps/web/.next/static/chunks/app/goals/page-b89f11b766408d3b.js +0 -1
  40. /package/.next/standalone/apps/web/.next/static/{KXdgQeTs8Pce55pRT4jbm → ZxT3BYruwHLcPbJZy9P5C}/_buildManifest.js +0 -0
  41. /package/.next/standalone/apps/web/.next/static/{KXdgQeTs8Pce55pRT4jbm → ZxT3BYruwHLcPbJZy9P5C}/_ssgManifest.js +0 -0
@@ -0,0 +1 @@
1
+ (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[341],{960:(e,t,a)=>{"use strict";a.d(t,{Jp:()=>s,Mu:()=>r,Pb:()=>l,XR:()=>c});async function n(e,t){let a=await fetch("".concat("").concat(e),{cache:"no-store",...t,headers:{...(null==t?void 0:t.body)!=null?{"content-type":"application/json"}:{},...null==t?void 0:t.headers}});if(!a.ok){let t="";try{t=JSON.stringify(await a.json())}catch(e){}throw Error("API ".concat(a.status," ").concat(e," ").concat(t))}if(204!==a.status)return a.json()}let r={list:function(){let e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];return n("/api/foods".concat(e?"?includeArchived=true":""))},create:e=>n("/api/foods",{method:"POST",body:JSON.stringify(e)}),update:(e,t)=>n("/api/foods/".concat(e),{method:"PATCH",body:JSON.stringify(t)})},s={list:e=>{let t=new URLSearchParams;(null==e?void 0:e.from)&&t.set("from",e.from),(null==e?void 0:e.to)&&t.set("to",e.to),(null==e?void 0:e.mealType)&&t.set("mealType",e.mealType);let a=t.toString();return n("/api/records".concat(a?"?".concat(a):""))},create:e=>n("/api/records",{method:"POST",body:JSON.stringify(e)}),createBatch:e=>n("/api/records/batch",{method:"POST",body:JSON.stringify({items:e})}),update:(e,t)=>n("/api/records/".concat(e),{method:"PATCH",body:JSON.stringify(t)}),remove:e=>n("/api/records/".concat(e),{method:"DELETE"})},l={list:()=>n("/api/goals"),current:()=>n("/api/goals/current"),create:e=>n("/api/goals",{method:"POST",body:JSON.stringify(e)}),remove:e=>n("/api/goals/".concat(e),{method:"DELETE"})},c={get:(e,t)=>{let a=new URLSearchParams({period:e});return t&&a.set("date",t),n("/api/summary?".concat(a.toString()))}}},3143:(e,t,a)=>{Promise.resolve().then(a.bind(a,5913))},4804:(e,t,a)=>{"use strict";function n(e){let t=e.getFullYear(),a=String(e.getMonth()+1).padStart(2,"0"),n=String(e.getDate()).padStart(2,"0");return"".concat(t,"-").concat(a,"-").concat(n)}function r(e){let t=new Date(e);return t.setHours(0,0,0,0),t}function s(e){let t=r(e);return t.setDate(t.getDate()+1),t}function l(e){let t=e.getTimezoneOffset();return new Date(e.getTime()-6e4*t).toISOString().slice(0,16)}function c(e){let t=new Date(e);return"".concat(String(t.getHours()).padStart(2,"0"),":").concat(String(t.getMinutes()).padStart(2,"0"))}a.d(t,{D0:()=>s,JN:()=>n,fU:()=>c,iI:()=>l,ol:()=>r})},5307:(e,t,a)=>{"use strict";a.d(t,{$n:()=>l,D0:()=>o,Zp:()=>r,_x:()=>s,l6:()=>i,pd:()=>c});var n=a(5155);function r(e){let{children:t,className:a=""}=e;return(0,n.jsx)("div",{className:"rounded-xl border border-neutral-200 bg-white p-5 shadow-sm ".concat(a),children:t})}function s(e){let{children:t}=e;return(0,n.jsx)("h2",{className:"mb-3 text-base font-semibold text-neutral-800",children:t})}function l(e){let{children:t,variant:a="primary",className:r="",...s}=e;return(0,n.jsx)("button",{className:"rounded-md px-3 py-1.5 text-sm font-medium transition disabled:cursor-not-allowed ".concat({primary:"bg-neutral-900 text-white hover:bg-neutral-700 disabled:bg-neutral-400",ghost:"bg-neutral-100 text-neutral-700 hover:bg-neutral-200",danger:"text-red-600 hover:bg-red-50"}[a]," ").concat(r),...s,children:t})}function c(e){return(0,n.jsx)("input",{className:"w-full rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-sm outline-none focus:border-neutral-500",...e})}function i(e){return(0,n.jsx)("select",{className:"w-full rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-sm outline-none focus:border-neutral-500",...e})}function o(e){let{label:t,children:a}=e;return(0,n.jsxs)("label",{className:"block",children:[(0,n.jsx)("span",{className:"mb-1 block text-xs font-medium text-neutral-500",children:t}),a]})}},5913:(e,t,a)=>{"use strict";a.d(t,{GoalManager:()=>i});var n=a(5155),r=a(2115),s=a(960),l=a(4804),c=a(5307);function i(){let[e,t]=(0,r.useState)([]),[a,i]=(0,r.useState)(null),[o,d]=(0,r.useState)(!0),[u,m]=(0,r.useState)(null),[x,h]=(0,r.useState)(""),[p,f]=(0,r.useState)((0,l.JN)(new Date)),[g,b]=(0,r.useState)(!1);async function y(){let[e,a]=await Promise.all([s.Pb.list(),s.Pb.current()]);t(e),i(a)}async function j(e){e.preventDefault(),b(!0),m(null);try{await s.Pb.create({dailyTarget:Number(x),effectiveFrom:new Date("".concat(p,"T00:00:00")).toISOString()}),h(""),await y()}catch(e){m(String(e))}finally{b(!1)}}async function S(e){confirm("この目標(".concat(e.dailyTarget," kcal)を削除しますか?"))&&(await s.Pb.remove(e.id),await y())}return(0,r.useEffect)(()=>{y().catch(e=>m(String(e))).finally(()=>d(!1))},[]),(0,n.jsxs)("div",{className:"space-y-6",children:[u&&(0,n.jsx)("div",{className:"rounded-md bg-red-50 px-3 py-2 text-sm text-red-700",children:u}),(0,n.jsxs)(c.Zp,{children:[(0,n.jsx)(c._x,{children:"現在の目標"}),o?(0,n.jsx)("p",{className:"text-sm text-neutral-500",children:"読み込み中…"}):a?(0,n.jsxs)("div",{className:"flex items-end gap-2",children:[(0,n.jsx)("span",{className:"text-3xl font-bold",children:a.dailyTarget.toLocaleString()}),(0,n.jsxs)("span",{className:"text-sm text-neutral-500",children:["kcal / 日(",new Date(a.effectiveFrom).toLocaleDateString("ja-JP")," 〜 適用中)"]})]}):(0,n.jsx)("p",{className:"text-sm text-neutral-500",children:"まだ目標が設定されていません。"})]}),(0,n.jsxs)(c.Zp,{children:[(0,n.jsx)(c._x,{children:"目標を設定 / 変更"}),(0,n.jsxs)("form",{onSubmit:j,className:"grid grid-cols-2 gap-3",children:[(0,n.jsx)(c.D0,{label:"1日の目標カロリー (kcal)",children:(0,n.jsx)(c.pd,{type:"number",min:"0",value:x,onChange:e=>h(e.target.value),placeholder:"1500",required:!0})}),(0,n.jsx)(c.D0,{label:"適用開始日",children:(0,n.jsx)(c.pd,{type:"date",value:p,onChange:e=>f(e.target.value)})}),(0,n.jsxs)("div",{className:"col-span-2",children:[(0,n.jsx)(c.$n,{type:"submit",disabled:g,children:g?"保存中…":"この目標を保存"}),(0,n.jsx)("p",{className:"mt-2 text-xs text-neutral-500",children:"※ 適用開始日ごとに履歴が残り、各日はその日時点の目標で評価されます。"})]})]})]}),(0,n.jsxs)(c.Zp,{children:[(0,n.jsx)(c._x,{children:"目標の履歴"}),0===e.length?(0,n.jsx)("p",{className:"text-sm text-neutral-500",children:"履歴はありません。"}):(0,n.jsx)("ul",{className:"divide-y divide-neutral-100",children:e.map(e=>(0,n.jsxs)("li",{className:"flex items-center justify-between py-2 text-sm",children:[(0,n.jsxs)("div",{children:[(0,n.jsxs)("span",{className:"font-medium",children:[e.dailyTarget.toLocaleString()," kcal/日"]}),(0,n.jsxs)("span",{className:"ml-2 text-neutral-500",children:[new Date(e.effectiveFrom).toLocaleDateString("ja-JP")," 〜"]}),(null==a?void 0:a.id)===e.id&&(0,n.jsx)("span",{className:"ml-2 rounded bg-emerald-100 px-1.5 py-0.5 text-xs text-emerald-700",children:"適用中"})]}),(0,n.jsx)(c.$n,{variant:"danger",onClick:()=>S(e),children:"削除"})]},e.id))})]})]})}}},e=>{e.O(0,[441,255,358],()=>e(e.s=3143)),_N_E=e.O()}]);
@@ -1 +1 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[974],{674:(e,t,a)=>{"use strict";a.d(t,{RecordEntry:()=>m});var n=a(5155),s=a(2115),l=a(960);let r=["breakfast","lunch","dinner","snack"],i={breakfast:"朝食",lunch:"昼食",dinner:"夕食",snack:"間食"};var c=a(4804),d=a(5307);function o(e){let{record:t,onChanged:a}=e,[o,u]=(0,s.useState)(!1),[m,h]=(0,s.useState)(t.name),[x,p]=(0,s.useState)(String(t.calories)),[g,b]=(0,s.useState)(t.mealType),[f,j]=(0,s.useState)((0,c.iI)(new Date(t.consumedAt))),[v,y]=(0,s.useState)(!1),[S,N]=(0,s.useState)(null);async function k(){y(!0),N(null);try{await l.Jp.update(t.id,{name:m,calories:Number(x),mealType:g,consumedAt:new Date(f).toISOString()}),u(!1),await a()}catch(e){N(String(e))}finally{y(!1)}}async function w(){y(!0);try{await l.Jp.remove(t.id),await a()}finally{y(!1)}}return o?(0,n.jsxs)("li",{className:"py-2 text-sm",children:[(0,n.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,n.jsx)(d.pd,{value:m,onChange:e=>h(e.target.value),placeholder:"食品名"}),(0,n.jsx)(d.pd,{type:"number",min:"0",value:x,onChange:e=>p(e.target.value),placeholder:"kcal"}),(0,n.jsx)(d.l6,{value:g,onChange:e=>b(e.target.value),children:r.map(e=>(0,n.jsx)("option",{value:e,children:i[e]},e))}),(0,n.jsx)(d.pd,{type:"datetime-local",value:f,onChange:e=>j(e.target.value)})]}),S&&(0,n.jsx)("p",{className:"mt-1 text-xs text-red-600",children:S}),(0,n.jsxs)("div",{className:"mt-2 flex gap-2",children:[(0,n.jsx)(d.$n,{onClick:k,disabled:v,children:v?"保存中…":"保存"}),(0,n.jsx)(d.$n,{variant:"ghost",onClick:()=>u(!1),disabled:v,children:"キャンセル"})]})]}):(0,n.jsxs)("li",{className:"flex items-center justify-between py-2 text-sm",children:[(0,n.jsxs)("div",{className:"flex items-center gap-2",children:[(0,n.jsx)("span",{className:"w-10 rounded bg-neutral-100 px-1 py-0.5 text-center text-xs text-neutral-600",children:i[t.mealType]}),(0,n.jsx)("span",{className:"text-neutral-400",children:(0,c.fU)(t.consumedAt)}),(0,n.jsx)("span",{className:"font-medium",children:t.name})]}),(0,n.jsxs)("div",{className:"flex items-center gap-2",children:[(0,n.jsxs)("span",{className:"tabular-nums",children:[t.calories," kcal"]}),(0,n.jsx)(d.$n,{variant:"ghost",onClick:function(){h(t.name),p(String(t.calories)),b(t.mealType),j((0,c.iI)(new Date(t.consumedAt))),N(null),u(!0)},disabled:v,children:"編集"}),(0,n.jsx)(d.$n,{variant:"danger",onClick:w,disabled:v,children:"削除"})]})]})}function u(e){return"master"===e.kind?Math.round(e.perServing*e.quantity):e.calories}function m(){var e;let[t,a]=(0,s.useState)([]),[m,h]=(0,s.useState)([]),[x,p]=(0,s.useState)(null),[g,b]=(0,s.useState)(!0),[f,j]=(0,s.useState)(null),[v,y]=(0,s.useState)("master"),[S,N]=(0,s.useState)(""),[k,w]=(0,s.useState)("1"),[D,C]=(0,s.useState)(""),[O,T]=(0,s.useState)(""),[P,I]=(0,s.useState)([]),[J,E]=(0,s.useState)("breakfast"),[A,L]=(0,s.useState)((0,c.iI)(new Date)),[M,_]=(0,s.useState)(!1),$=(0,s.useRef)(0),q=()=>String(++$.current);async function R(){let e=new Date,[t,n,s]=await Promise.all([l.Mu.list(),l.Jp.list({from:(0,c.ol)(e).toISOString(),to:(0,c.D0)(e).toISOString()}),l.Pb.current()]);a(t),h(n),p(s),!S&&t.length>0&&N(t[0].id)}(0,s.useEffect)(()=>{R().catch(e=>j(String(e))).finally(()=>b(!1))},[]);let H=(0,s.useMemo)(()=>m.reduce((e,t)=>e+t.calories,0),[m]),U=null!=(e=null==x?void 0:x.dailyTarget)?e:null,Z=U?Math.min(100,Math.round(H/U*100)):null,F=t.find(e=>e.id===S),B="master"===v&&F?Math.round(F.caloriesPerServing*(Number(k)||0)):null,z=(0,s.useMemo)(()=>P.reduce((e,t)=>e+u(t),0),[P]);async function X(){if(0===P.length)return void j("食品を追加してください");_(!0),j(null);try{let e=new Date(A).toISOString();await l.Jp.createBatch(P.map(t=>"master"===t.kind?{foodId:t.foodId,quantity:t.quantity,mealType:J,consumedAt:e}:{name:t.name,calories:t.calories,mealType:J,consumedAt:e})),I([]),await R()}catch(e){j(String(e))}finally{_(!1)}}return g?(0,n.jsx)("p",{className:"text-sm text-neutral-500",children:"読み込み中…"}):(0,n.jsxs)("div",{className:"space-y-6",children:[f&&(0,n.jsx)("div",{className:"rounded-md bg-red-50 px-3 py-2 text-sm text-red-700",children:f}),(0,n.jsxs)(d.Zp,{children:[(0,n.jsx)(d._x,{children:"今日の摂取"}),(0,n.jsxs)("div",{className:"flex items-end gap-2",children:[(0,n.jsx)("span",{className:"text-3xl font-bold",children:H.toLocaleString()}),(0,n.jsxs)("span",{className:"text-sm text-neutral-500",children:["kcal",U?" / 目標 ".concat(U.toLocaleString()," kcal"):"(目標未設定)"]})]}),null!==Z&&(0,n.jsx)("div",{className:"mt-3 h-2 w-full overflow-hidden rounded-full bg-neutral-200",children:(0,n.jsx)("div",{className:"h-full ".concat(H>(null!=U?U:0)?"bg-red-500":"bg-emerald-500"),style:{width:"".concat(Z,"%")}})})]}),(0,n.jsxs)(d.Zp,{children:[(0,n.jsx)(d._x,{children:"記録する"}),(0,n.jsx)("p",{className:"mb-3 text-xs text-neutral-500",children:"食品を追加してから「まとめて記録」。米とサバ缶のように複数をまとめて1つの食事として登録できます。"}),(0,n.jsxs)("div",{className:"mb-3 flex gap-1 text-sm",children:[(0,n.jsx)("button",{type:"button",onClick:()=>y("master"),className:"rounded-md px-3 py-1 ".concat("master"===v?"bg-neutral-900 text-white":"bg-neutral-100"),children:"マスタから"}),(0,n.jsx)("button",{type:"button",onClick:()=>y("adhoc"),className:"rounded-md px-3 py-1 ".concat("adhoc"===v?"bg-neutral-900 text-white":"bg-neutral-100"),children:"その場で入力"})]}),(0,n.jsxs)("form",{onSubmit:function(e){if(e.preventDefault(),j(null),"master"===v){let e=t.find(e=>e.id===S);if(!e)return void j("食品を選択してください");I(t=>[...t,{key:q(),kind:"master",foodId:e.id,name:e.name,perServing:e.caloriesPerServing,quantity:Number(k)||1}])}else{if(!D.trim()||""===O)return void j("食品名とカロリーを入力してください");I(e=>[...e,{key:q(),kind:"adhoc",name:D.trim(),calories:Number(O)}]),C(""),T("")}},className:"grid grid-cols-2 gap-3",children:["master"===v?(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)("div",{className:"col-span-2",children:(0,n.jsx)(d.D0,{label:"食品",children:0===t.length?(0,n.jsx)("p",{className:"text-sm text-neutral-500",children:"食品マスタが空です。「食品マスタ」から登録してください。"}):(0,n.jsx)(d.l6,{value:S,onChange:e=>N(e.target.value),children:t.map(e=>(0,n.jsxs)("option",{value:e.id,children:[e.name,"(",e.caloriesPerServing," kcal",e.unitLabel?" / ".concat(e.unitLabel):"",")"]},e.id))})})}),(0,n.jsx)(d.D0,{label:"数量",children:(0,n.jsx)(d.pd,{type:"number",step:"0.1",min:"0",value:k,onChange:e=>w(e.target.value)})}),(0,n.jsx)("div",{className:"flex items-end pb-1",children:(0,n.jsx)("p",{className:"text-sm text-neutral-500",children:null!==B?"= ".concat(B," kcal"):""})})]}):(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(d.D0,{label:"食品名",children:(0,n.jsx)(d.pd,{value:D,onChange:e=>C(e.target.value)})}),(0,n.jsx)(d.D0,{label:"カロリー (kcal)",children:(0,n.jsx)(d.pd,{type:"number",min:"0",value:O,onChange:e=>T(e.target.value)})})]}),(0,n.jsx)("div",{className:"col-span-2",children:(0,n.jsx)(d.$n,{type:"submit",variant:"ghost",disabled:"master"===v&&0===t.length,children:"+ 食品を追加"})})]}),P.length>0&&(0,n.jsxs)("div",{className:"mt-3 rounded-lg border border-neutral-200 bg-neutral-50 p-3",children:[(0,n.jsx)("ul",{className:"divide-y divide-neutral-200",children:P.map(e=>(0,n.jsxs)("li",{className:"flex items-center justify-between py-1.5 text-sm",children:[(0,n.jsxs)("span",{children:[(0,n.jsx)("span",{className:"font-medium",children:e.name}),"master"===e.kind&&1!==e.quantity&&(0,n.jsxs)("span",{className:"ml-1 text-neutral-500",children:["\xd7",e.quantity]})]}),(0,n.jsxs)("span",{className:"flex items-center gap-3",children:[(0,n.jsxs)("span",{className:"tabular-nums text-neutral-600",children:[u(e)," kcal"]}),(0,n.jsx)("button",{type:"button",onClick:()=>{var t;return t=e.key,void I(e=>e.filter(e=>e.key!==t))},className:"rounded px-1.5 text-red-600 hover:bg-red-50","aria-label":"削除",children:"\xd7"})]})]},e.key))}),(0,n.jsxs)("div",{className:"mt-2 flex justify-between border-t border-neutral-200 pt-2 text-sm font-medium",children:[(0,n.jsxs)("span",{children:["合計(",P.length,"件)"]}),(0,n.jsxs)("span",{className:"tabular-nums",children:[z.toLocaleString()," kcal"]})]})]}),(0,n.jsxs)("div",{className:"mt-3 grid grid-cols-2 gap-3",children:[(0,n.jsx)(d.D0,{label:"区分",children:(0,n.jsx)(d.l6,{value:J,onChange:e=>E(e.target.value),children:r.map(e=>(0,n.jsx)("option",{value:e,children:i[e]},e))})}),(0,n.jsx)(d.D0,{label:"摂取日時",children:(0,n.jsx)(d.pd,{type:"datetime-local",value:A,onChange:e=>L(e.target.value)})}),(0,n.jsx)("div",{className:"col-span-2",children:(0,n.jsx)(d.$n,{type:"button",onClick:X,disabled:M||0===P.length,children:M?"記録中…":"まとめて記録".concat(P.length>0?"(".concat(P.length,"件・").concat(z.toLocaleString()," kcal)"):"")})})]})]}),(0,n.jsxs)(d.Zp,{children:[(0,n.jsx)(d._x,{children:"今日の記録"}),0===m.length?(0,n.jsx)("p",{className:"text-sm text-neutral-500",children:"まだ記録がありません。"}):(0,n.jsx)("ul",{className:"divide-y divide-neutral-100",children:m.map(e=>(0,n.jsx)(o,{record:e,onChanged:R},e.id))})]})]})}},960:(e,t,a)=>{"use strict";a.d(t,{Jp:()=>r,Mu:()=>l,Pb:()=>i,XR:()=>c});let n="http://localhost:6251";async function s(e,t){let a=await fetch("".concat(n).concat(e),{headers:{"content-type":"application/json"},cache:"no-store",...t});if(!a.ok){let t="";try{t=JSON.stringify(await a.json())}catch(e){}throw Error("API ".concat(a.status," ").concat(e," ").concat(t))}if(204!==a.status)return a.json()}let l={list:function(){let e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];return s("/api/foods".concat(e?"?includeArchived=true":""))},create:e=>s("/api/foods",{method:"POST",body:JSON.stringify(e)}),update:(e,t)=>s("/api/foods/".concat(e),{method:"PATCH",body:JSON.stringify(t)})},r={list:e=>{let t=new URLSearchParams;(null==e?void 0:e.from)&&t.set("from",e.from),(null==e?void 0:e.to)&&t.set("to",e.to),(null==e?void 0:e.mealType)&&t.set("mealType",e.mealType);let a=t.toString();return s("/api/records".concat(a?"?".concat(a):""))},create:e=>s("/api/records",{method:"POST",body:JSON.stringify(e)}),createBatch:e=>s("/api/records/batch",{method:"POST",body:JSON.stringify({items:e})}),update:(e,t)=>s("/api/records/".concat(e),{method:"PATCH",body:JSON.stringify(t)}),remove:e=>s("/api/records/".concat(e),{method:"DELETE"})},i={list:()=>s("/api/goals"),current:()=>s("/api/goals/current"),create:e=>s("/api/goals",{method:"POST",body:JSON.stringify(e)}),remove:e=>s("/api/goals/".concat(e),{method:"DELETE"})},c={get:(e,t)=>{let a=new URLSearchParams({period:e});return t&&a.set("date",t),s("/api/summary?".concat(a.toString()))}}},3545:(e,t,a)=>{Promise.resolve().then(a.bind(a,674))},4804:(e,t,a)=>{"use strict";function n(e){let t=e.getFullYear(),a=String(e.getMonth()+1).padStart(2,"0"),n=String(e.getDate()).padStart(2,"0");return"".concat(t,"-").concat(a,"-").concat(n)}function s(e){let t=new Date(e);return t.setHours(0,0,0,0),t}function l(e){let t=s(e);return t.setDate(t.getDate()+1),t}function r(e){let t=e.getTimezoneOffset();return new Date(e.getTime()-6e4*t).toISOString().slice(0,16)}function i(e){let t=new Date(e);return"".concat(String(t.getHours()).padStart(2,"0"),":").concat(String(t.getMinutes()).padStart(2,"0"))}a.d(t,{D0:()=>l,JN:()=>n,fU:()=>i,iI:()=>r,ol:()=>s})},5307:(e,t,a)=>{"use strict";a.d(t,{$n:()=>r,D0:()=>d,Zp:()=>s,_x:()=>l,l6:()=>c,pd:()=>i});var n=a(5155);function s(e){let{children:t,className:a=""}=e;return(0,n.jsx)("div",{className:"rounded-xl border border-neutral-200 bg-white p-5 shadow-sm ".concat(a),children:t})}function l(e){let{children:t}=e;return(0,n.jsx)("h2",{className:"mb-3 text-base font-semibold text-neutral-800",children:t})}function r(e){let{children:t,variant:a="primary",className:s="",...l}=e;return(0,n.jsx)("button",{className:"rounded-md px-3 py-1.5 text-sm font-medium transition disabled:cursor-not-allowed ".concat({primary:"bg-neutral-900 text-white hover:bg-neutral-700 disabled:bg-neutral-400",ghost:"bg-neutral-100 text-neutral-700 hover:bg-neutral-200",danger:"text-red-600 hover:bg-red-50"}[a]," ").concat(s),...l,children:t})}function i(e){return(0,n.jsx)("input",{className:"w-full rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-sm outline-none focus:border-neutral-500",...e})}function c(e){return(0,n.jsx)("select",{className:"w-full rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-sm outline-none focus:border-neutral-500",...e})}function d(e){let{label:t,children:a}=e;return(0,n.jsxs)("label",{className:"block",children:[(0,n.jsx)("span",{className:"mb-1 block text-xs font-medium text-neutral-500",children:t}),a]})}}},e=>{e.O(0,[441,255,358],()=>e(e.s=3545)),_N_E=e.O()}]);
1
+ (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[974],{674:(e,t,a)=>{"use strict";a.d(t,{RecordEntry:()=>m});var n=a(5155),l=a(2115),s=a(960);let r=["breakfast","lunch","dinner","snack"],i={breakfast:"朝食",lunch:"昼食",dinner:"夕食",snack:"間食"};var c=a(4804),d=a(5307);function o(e){let{record:t,onChanged:a}=e,[o,u]=(0,l.useState)(!1),[m,h]=(0,l.useState)(t.name),[x,p]=(0,l.useState)(String(t.calories)),[g,b]=(0,l.useState)(t.mealType),[f,j]=(0,l.useState)((0,c.iI)(new Date(t.consumedAt))),[v,y]=(0,l.useState)(!1),[S,N]=(0,l.useState)(null);async function k(){y(!0),N(null);try{await s.Jp.update(t.id,{name:m,calories:Number(x),mealType:g,consumedAt:new Date(f).toISOString()}),u(!1),await a()}catch(e){N(String(e))}finally{y(!1)}}async function w(){y(!0);try{await s.Jp.remove(t.id),await a()}finally{y(!1)}}return o?(0,n.jsxs)("li",{className:"py-2 text-sm",children:[(0,n.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,n.jsx)(d.pd,{value:m,onChange:e=>h(e.target.value),placeholder:"食品名"}),(0,n.jsx)(d.pd,{type:"number",min:"0",value:x,onChange:e=>p(e.target.value),placeholder:"kcal"}),(0,n.jsx)(d.l6,{value:g,onChange:e=>b(e.target.value),children:r.map(e=>(0,n.jsx)("option",{value:e,children:i[e]},e))}),(0,n.jsx)(d.pd,{type:"datetime-local",value:f,onChange:e=>j(e.target.value)})]}),S&&(0,n.jsx)("p",{className:"mt-1 text-xs text-red-600",children:S}),(0,n.jsxs)("div",{className:"mt-2 flex gap-2",children:[(0,n.jsx)(d.$n,{onClick:k,disabled:v,children:v?"保存中…":"保存"}),(0,n.jsx)(d.$n,{variant:"ghost",onClick:()=>u(!1),disabled:v,children:"キャンセル"})]})]}):(0,n.jsxs)("li",{className:"flex items-center justify-between py-2 text-sm",children:[(0,n.jsxs)("div",{className:"flex items-center gap-2",children:[(0,n.jsx)("span",{className:"w-10 rounded bg-neutral-100 px-1 py-0.5 text-center text-xs text-neutral-600",children:i[t.mealType]}),(0,n.jsx)("span",{className:"text-neutral-400",children:(0,c.fU)(t.consumedAt)}),(0,n.jsx)("span",{className:"font-medium",children:t.name})]}),(0,n.jsxs)("div",{className:"flex items-center gap-2",children:[(0,n.jsxs)("span",{className:"tabular-nums",children:[t.calories," kcal"]}),(0,n.jsx)(d.$n,{variant:"ghost",onClick:function(){h(t.name),p(String(t.calories)),b(t.mealType),j((0,c.iI)(new Date(t.consumedAt))),N(null),u(!0)},disabled:v,children:"編集"}),(0,n.jsx)(d.$n,{variant:"danger",onClick:w,disabled:v,children:"削除"})]})]})}function u(e){return"master"===e.kind?Math.round(e.perServing*e.quantity):e.calories}function m(){var e;let[t,a]=(0,l.useState)([]),[m,h]=(0,l.useState)([]),[x,p]=(0,l.useState)(null),[g,b]=(0,l.useState)(!0),[f,j]=(0,l.useState)(null),[v,y]=(0,l.useState)("master"),[S,N]=(0,l.useState)(""),[k,w]=(0,l.useState)("1"),[D,C]=(0,l.useState)(""),[O,T]=(0,l.useState)(""),[P,I]=(0,l.useState)([]),[J,E]=(0,l.useState)("breakfast"),[A,L]=(0,l.useState)((0,c.iI)(new Date)),[M,_]=(0,l.useState)(!1),$=(0,l.useRef)(0),q=()=>String(++$.current);async function R(){let e=new Date,[t,n,l]=await Promise.all([s.Mu.list(),s.Jp.list({from:(0,c.ol)(e).toISOString(),to:(0,c.D0)(e).toISOString()}),s.Pb.current()]);a(t),h(n),p(l),!S&&t.length>0&&N(t[0].id)}(0,l.useEffect)(()=>{R().catch(e=>j(String(e))).finally(()=>b(!1))},[]);let H=(0,l.useMemo)(()=>m.reduce((e,t)=>e+t.calories,0),[m]),U=null!=(e=null==x?void 0:x.dailyTarget)?e:null,Z=U?Math.min(100,Math.round(H/U*100)):null,F=t.find(e=>e.id===S),B="master"===v&&F?Math.round(F.caloriesPerServing*(Number(k)||0)):null,z=(0,l.useMemo)(()=>P.reduce((e,t)=>e+u(t),0),[P]);async function X(){if(0===P.length)return void j("食品を追加してください");_(!0),j(null);try{let e=new Date(A).toISOString();await s.Jp.createBatch(P.map(t=>"master"===t.kind?{foodId:t.foodId,quantity:t.quantity,mealType:J,consumedAt:e}:{name:t.name,calories:t.calories,mealType:J,consumedAt:e})),I([]),await R()}catch(e){j(String(e))}finally{_(!1)}}return g?(0,n.jsx)("p",{className:"text-sm text-neutral-500",children:"読み込み中…"}):(0,n.jsxs)("div",{className:"space-y-6",children:[f&&(0,n.jsx)("div",{className:"rounded-md bg-red-50 px-3 py-2 text-sm text-red-700",children:f}),(0,n.jsxs)(d.Zp,{children:[(0,n.jsx)(d._x,{children:"今日の摂取"}),(0,n.jsxs)("div",{className:"flex items-end gap-2",children:[(0,n.jsx)("span",{className:"text-3xl font-bold",children:H.toLocaleString()}),(0,n.jsxs)("span",{className:"text-sm text-neutral-500",children:["kcal",U?" / 目標 ".concat(U.toLocaleString()," kcal"):"(目標未設定)"]})]}),null!==Z&&(0,n.jsx)("div",{className:"mt-3 h-2 w-full overflow-hidden rounded-full bg-neutral-200",children:(0,n.jsx)("div",{className:"h-full ".concat(H>(null!=U?U:0)?"bg-red-500":"bg-emerald-500"),style:{width:"".concat(Z,"%")}})})]}),(0,n.jsxs)(d.Zp,{children:[(0,n.jsx)(d._x,{children:"記録する"}),(0,n.jsx)("p",{className:"mb-3 text-xs text-neutral-500",children:"食品を追加してから「まとめて記録」。米とサバ缶のように複数をまとめて1つの食事として登録できます。"}),(0,n.jsxs)("div",{className:"mb-3 flex gap-1 text-sm",children:[(0,n.jsx)("button",{type:"button",onClick:()=>y("master"),className:"rounded-md px-3 py-1 ".concat("master"===v?"bg-neutral-900 text-white":"bg-neutral-100"),children:"マスタから"}),(0,n.jsx)("button",{type:"button",onClick:()=>y("adhoc"),className:"rounded-md px-3 py-1 ".concat("adhoc"===v?"bg-neutral-900 text-white":"bg-neutral-100"),children:"その場で入力"})]}),(0,n.jsxs)("form",{onSubmit:function(e){if(e.preventDefault(),j(null),"master"===v){let e=t.find(e=>e.id===S);if(!e)return void j("食品を選択してください");I(t=>[...t,{key:q(),kind:"master",foodId:e.id,name:e.name,perServing:e.caloriesPerServing,quantity:Number(k)||1}])}else{if(!D.trim()||""===O)return void j("食品名とカロリーを入力してください");I(e=>[...e,{key:q(),kind:"adhoc",name:D.trim(),calories:Number(O)}]),C(""),T("")}},className:"grid grid-cols-2 gap-3",children:["master"===v?(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)("div",{className:"col-span-2",children:(0,n.jsx)(d.D0,{label:"食品",children:0===t.length?(0,n.jsx)("p",{className:"text-sm text-neutral-500",children:"食品マスタが空です。「食品マスタ」から登録してください。"}):(0,n.jsx)(d.l6,{value:S,onChange:e=>N(e.target.value),children:t.map(e=>(0,n.jsxs)("option",{value:e.id,children:[e.name,"(",e.caloriesPerServing," kcal",e.unitLabel?" / ".concat(e.unitLabel):"",")"]},e.id))})})}),(0,n.jsx)(d.D0,{label:"数量",children:(0,n.jsx)(d.pd,{type:"number",step:"0.1",min:"0",value:k,onChange:e=>w(e.target.value)})}),(0,n.jsx)("div",{className:"flex items-end pb-1",children:(0,n.jsx)("p",{className:"text-sm text-neutral-500",children:null!==B?"= ".concat(B," kcal"):""})})]}):(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(d.D0,{label:"食品名",children:(0,n.jsx)(d.pd,{value:D,onChange:e=>C(e.target.value)})}),(0,n.jsx)(d.D0,{label:"カロリー (kcal)",children:(0,n.jsx)(d.pd,{type:"number",min:"0",value:O,onChange:e=>T(e.target.value)})})]}),(0,n.jsx)("div",{className:"col-span-2",children:(0,n.jsx)(d.$n,{type:"submit",variant:"ghost",disabled:"master"===v&&0===t.length,children:"+ 食品を追加"})})]}),P.length>0&&(0,n.jsxs)("div",{className:"mt-3 rounded-lg border border-neutral-200 bg-neutral-50 p-3",children:[(0,n.jsx)("ul",{className:"divide-y divide-neutral-200",children:P.map(e=>(0,n.jsxs)("li",{className:"flex items-center justify-between py-1.5 text-sm",children:[(0,n.jsxs)("span",{children:[(0,n.jsx)("span",{className:"font-medium",children:e.name}),"master"===e.kind&&1!==e.quantity&&(0,n.jsxs)("span",{className:"ml-1 text-neutral-500",children:["\xd7",e.quantity]})]}),(0,n.jsxs)("span",{className:"flex items-center gap-3",children:[(0,n.jsxs)("span",{className:"tabular-nums text-neutral-600",children:[u(e)," kcal"]}),(0,n.jsx)("button",{type:"button",onClick:()=>{var t;return t=e.key,void I(e=>e.filter(e=>e.key!==t))},className:"rounded px-1.5 text-red-600 hover:bg-red-50","aria-label":"削除",children:"\xd7"})]})]},e.key))}),(0,n.jsxs)("div",{className:"mt-2 flex justify-between border-t border-neutral-200 pt-2 text-sm font-medium",children:[(0,n.jsxs)("span",{children:["合計(",P.length,"件)"]}),(0,n.jsxs)("span",{className:"tabular-nums",children:[z.toLocaleString()," kcal"]})]})]}),(0,n.jsxs)("div",{className:"mt-3 grid grid-cols-2 gap-3",children:[(0,n.jsx)(d.D0,{label:"区分",children:(0,n.jsx)(d.l6,{value:J,onChange:e=>E(e.target.value),children:r.map(e=>(0,n.jsx)("option",{value:e,children:i[e]},e))})}),(0,n.jsx)(d.D0,{label:"摂取日時",children:(0,n.jsx)(d.pd,{type:"datetime-local",value:A,onChange:e=>L(e.target.value)})}),(0,n.jsx)("div",{className:"col-span-2",children:(0,n.jsx)(d.$n,{type:"button",onClick:X,disabled:M||0===P.length,children:M?"記録中…":"まとめて記録".concat(P.length>0?"(".concat(P.length,"件・").concat(z.toLocaleString()," kcal)"):"")})})]})]}),(0,n.jsxs)(d.Zp,{children:[(0,n.jsx)(d._x,{children:"今日の記録"}),0===m.length?(0,n.jsx)("p",{className:"text-sm text-neutral-500",children:"まだ記録がありません。"}):(0,n.jsx)("ul",{className:"divide-y divide-neutral-100",children:m.map(e=>(0,n.jsx)(o,{record:e,onChanged:R},e.id))})]})]})}},960:(e,t,a)=>{"use strict";a.d(t,{Jp:()=>s,Mu:()=>l,Pb:()=>r,XR:()=>i});async function n(e,t){let a=await fetch("".concat("").concat(e),{cache:"no-store",...t,headers:{...(null==t?void 0:t.body)!=null?{"content-type":"application/json"}:{},...null==t?void 0:t.headers}});if(!a.ok){let t="";try{t=JSON.stringify(await a.json())}catch(e){}throw Error("API ".concat(a.status," ").concat(e," ").concat(t))}if(204!==a.status)return a.json()}let l={list:function(){let e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];return n("/api/foods".concat(e?"?includeArchived=true":""))},create:e=>n("/api/foods",{method:"POST",body:JSON.stringify(e)}),update:(e,t)=>n("/api/foods/".concat(e),{method:"PATCH",body:JSON.stringify(t)})},s={list:e=>{let t=new URLSearchParams;(null==e?void 0:e.from)&&t.set("from",e.from),(null==e?void 0:e.to)&&t.set("to",e.to),(null==e?void 0:e.mealType)&&t.set("mealType",e.mealType);let a=t.toString();return n("/api/records".concat(a?"?".concat(a):""))},create:e=>n("/api/records",{method:"POST",body:JSON.stringify(e)}),createBatch:e=>n("/api/records/batch",{method:"POST",body:JSON.stringify({items:e})}),update:(e,t)=>n("/api/records/".concat(e),{method:"PATCH",body:JSON.stringify(t)}),remove:e=>n("/api/records/".concat(e),{method:"DELETE"})},r={list:()=>n("/api/goals"),current:()=>n("/api/goals/current"),create:e=>n("/api/goals",{method:"POST",body:JSON.stringify(e)}),remove:e=>n("/api/goals/".concat(e),{method:"DELETE"})},i={get:(e,t)=>{let a=new URLSearchParams({period:e});return t&&a.set("date",t),n("/api/summary?".concat(a.toString()))}}},3545:(e,t,a)=>{Promise.resolve().then(a.bind(a,674))},4804:(e,t,a)=>{"use strict";function n(e){let t=e.getFullYear(),a=String(e.getMonth()+1).padStart(2,"0"),n=String(e.getDate()).padStart(2,"0");return"".concat(t,"-").concat(a,"-").concat(n)}function l(e){let t=new Date(e);return t.setHours(0,0,0,0),t}function s(e){let t=l(e);return t.setDate(t.getDate()+1),t}function r(e){let t=e.getTimezoneOffset();return new Date(e.getTime()-6e4*t).toISOString().slice(0,16)}function i(e){let t=new Date(e);return"".concat(String(t.getHours()).padStart(2,"0"),":").concat(String(t.getMinutes()).padStart(2,"0"))}a.d(t,{D0:()=>s,JN:()=>n,fU:()=>i,iI:()=>r,ol:()=>l})},5307:(e,t,a)=>{"use strict";a.d(t,{$n:()=>r,D0:()=>d,Zp:()=>l,_x:()=>s,l6:()=>c,pd:()=>i});var n=a(5155);function l(e){let{children:t,className:a=""}=e;return(0,n.jsx)("div",{className:"rounded-xl border border-neutral-200 bg-white p-5 shadow-sm ".concat(a),children:t})}function s(e){let{children:t}=e;return(0,n.jsx)("h2",{className:"mb-3 text-base font-semibold text-neutral-800",children:t})}function r(e){let{children:t,variant:a="primary",className:l="",...s}=e;return(0,n.jsx)("button",{className:"rounded-md px-3 py-1.5 text-sm font-medium transition disabled:cursor-not-allowed ".concat({primary:"bg-neutral-900 text-white hover:bg-neutral-700 disabled:bg-neutral-400",ghost:"bg-neutral-100 text-neutral-700 hover:bg-neutral-200",danger:"text-red-600 hover:bg-red-50"}[a]," ").concat(l),...s,children:t})}function i(e){return(0,n.jsx)("input",{className:"w-full rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-sm outline-none focus:border-neutral-500",...e})}function c(e){return(0,n.jsx)("select",{className:"w-full rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-sm outline-none focus:border-neutral-500",...e})}function d(e){let{label:t,children:a}=e;return(0,n.jsxs)("label",{className:"block",children:[(0,n.jsx)("span",{className:"mb-1 block text-xs font-medium text-neutral-500",children:t}),a]})}}},e=>{e.O(0,[441,255,358],()=>e(e.s=3545)),_N_E=e.O()}]);
@@ -5,9 +5,9 @@
5
5
  "description": "meshi web UI (Next.js)",
6
6
  "type": "module",
7
7
  "scripts": {
8
- "dev": "next dev -H 0.0.0.0 -p ${MESHI_WEB_PORT:-5250}",
8
+ "dev": "next dev -H 0.0.0.0 -p ${MESHI_WEB_PORT:-5251}",
9
9
  "build": "next build",
10
- "start": "next start -p ${MESHI_WEB_PORT:-5250}",
10
+ "start": "next start -p ${MESHI_WEB_PORT:-5251}",
11
11
  "lint": "eslint",
12
12
  "type-check": "tsc --noEmit"
13
13
  },
package/dist/cli.js CHANGED
@@ -21,14 +21,17 @@ import { fileURLToPath } from 'node:url';
21
21
  * <pkg>/prisma/migrations/**
22
22
  * <pkg>/prisma.config.ts
23
23
  *
24
- * web build 時に NEXT_PUBLIC_MESHI_API_URL=http://localhost:6251 を焼き込んでいるため、
25
- * server は必ず 6251 で待ち受ける。web port PORT env で変更可能。
24
+ * Fastify が唯一の公開エントリ。API を直接処理し、それ以外(Next.js のページ/静的
25
+ * アセット)を内部の Next.js proxy する。web は相対 URL (same-origin) でビルド
26
+ * しているため、公開ポート (PUBLIC_PORT) は PORT env で自由に変更できる。
26
27
  */
27
28
  // ---------------------------------------------------------------------------
28
- // 固定ポート (web の API URL がビルド時に焼き込まれているため server は固定)
29
+ // ポート
30
+ // PUBLIC_PORT : ユーザーがブラウザで開く Fastify の公開ポート(変更可)
31
+ // WEB_PORT : 内部 Next.js のポート(Fastify から proxy。外部公開しない)
29
32
  // ---------------------------------------------------------------------------
30
- const SERVER_PORT = 6251;
31
- const WEB_PORT = process.env.PORT ?? '6250';
33
+ const PUBLIC_PORT = process.env.PORT ?? '5250';
34
+ const WEB_PORT = '5251';
32
35
  // ---------------------------------------------------------------------------
33
36
  // Braille-dots spinner
34
37
  // ---------------------------------------------------------------------------
@@ -110,8 +113,10 @@ const fastifyChild = spawn(process.execPath, [FASTIFY_ENTRY_JS], {
110
113
  env: {
111
114
  ...process.env,
112
115
  NODE_ENV: 'production',
113
- MESHI_SERVER_PORT: String(SERVER_PORT),
116
+ MESHI_SERVER_PORT: String(PUBLIC_PORT),
114
117
  MESHI_SERVER_HOST: '0.0.0.0',
118
+ // Fastify の proxy 先(内部 Next.js)
119
+ MESHI_WEB_PORT: WEB_PORT,
115
120
  },
116
121
  });
117
122
  const nextChild = spawn(process.execPath, [NEXT_STANDALONE_ENTRY], {
@@ -163,12 +168,12 @@ function pollHttp(port, urlPath, isReady) {
163
168
  });
164
169
  }
165
170
  Promise.all([
166
- pollHttp(SERVER_PORT, '/health', (s) => s === 200),
171
+ pollHttp(Number(PUBLIC_PORT), '/health', (s) => s === 200),
167
172
  pollHttp(Number(WEB_PORT), '/', (s) => s > 0 && s < 500),
168
173
  ]).then(() => {
169
174
  spinner.stop();
170
175
  console.log(MESHI_AA);
171
- const url = `http://localhost:${WEB_PORT}`;
176
+ const url = `http://localhost:${PUBLIC_PORT}`;
172
177
  // docker / headless / テスト時はブラウザを自動で開かない
173
178
  if (!isDocker && !process.env.MESHI_NO_OPEN) {
174
179
  const cmd = process.platform === 'darwin' ? 'open' : 'xdg-open';
@@ -1,4 +1,4 @@
1
- import cors from '@fastify/cors';
1
+ import httpProxy from '@fastify/http-proxy';
2
2
  import Fastify from 'fastify';
3
3
  import { prisma } from './lib/db.js';
4
4
  import { getDatabasePath } from './lib/data-path.js';
@@ -6,20 +6,36 @@ import { foodRoutes } from './routes/foods.js';
6
6
  import { recordRoutes } from './routes/records.js';
7
7
  import { goalRoutes } from './routes/goals.js';
8
8
  import { summaryRoutes } from './routes/summary.js';
9
- const PORT = Number(process.env.MESHI_SERVER_PORT ?? 5251);
9
+ // 日次集計は「日本時間 0 時」を境界に区切る。サーバーのプロセス TZ に依存すると
10
+ // (Docker や海外サーバーでは UTC のため)境界がずれて記録が前日に入ってしまう。
11
+ // アプリは JST 固定運用のため、TZ 未指定なら Asia/Tokyo を既定にする(明示指定は尊重)。
12
+ process.env.TZ ||= 'Asia/Tokyo';
13
+ const PORT = Number(process.env.MESHI_SERVER_PORT ?? 5250);
10
14
  const HOST = process.env.MESHI_SERVER_HOST ?? '0.0.0.0';
15
+ // 内部で動く Next.js のポート。Fastify が唯一の公開エントリとなり、
16
+ // API 以外のリクエストをここへ proxy する(同一オリジン化 → CORS 不要)。
17
+ const WEB_PORT = Number(process.env.MESHI_WEB_PORT ?? 5251);
18
+ const WEB_UPSTREAM = process.env.MESHI_WEB_UPSTREAM ?? `http://127.0.0.1:${WEB_PORT}`;
11
19
  const app = Fastify({
12
20
  logger: { level: process.env.NODE_ENV === 'development' ? 'info' : 'warn' },
13
21
  });
14
- await app.register(cors, { origin: true });
15
22
  app.get('/health', async () => ({
16
23
  status: 'ok',
17
24
  db: getDatabasePath(),
18
25
  }));
26
+ // API ルートは proxy のキャッチオールより先に登録する(より具体的な経路が優先される)。
19
27
  await app.register(foodRoutes, { prefix: '/api/foods' });
20
28
  await app.register(recordRoutes, { prefix: '/api/records' });
21
29
  await app.register(goalRoutes, { prefix: '/api/goals' });
22
30
  await app.register(summaryRoutes, { prefix: '/api/summary' });
31
+ // 上記以外(/, /_next/*, 静的アセット, webpack HMR の websocket 等)は内部 Next.js へ
32
+ // リバースプロキシする。Fastify を単一の公開エントリにすることでブラウザは常に同一
33
+ // オリジンと通信し、CORS が原理的に不要になる(Cloudflare 等の単一オリジン運用にも対応)。
34
+ await app.register(httpProxy, {
35
+ upstream: WEB_UPSTREAM,
36
+ // Next dev の webpack HMR は websocket を使うため、これもパススルーする。
37
+ websocket: true,
38
+ });
23
39
  const start = async () => {
24
40
  try {
25
41
  // 接続確認
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minimalcorp/meshi",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "摂取カロリーを記録し、目標に対する進捗を可視化するローカルツール。npx 一発で web + server を起動する。",
5
5
  "keywords": [
6
6
  "meshi",
@@ -44,12 +44,12 @@
44
44
  ],
45
45
  "scripts": {
46
46
  "build": "tsc -p tsconfig.json && chmod +x dist/cli.js",
47
- "prepack": "npm run build -w @meshi/server && NEXT_PUBLIC_MESHI_API_URL=http://localhost:6251 npm run build -w @meshi/web && npm run build && node scripts/bundle.mjs",
47
+ "prepack": "npm run build -w @meshi/server && NEXT_PUBLIC_MESHI_API_URL= npm run build -w @meshi/web && npm run build && node scripts/bundle.mjs",
48
48
  "lint": "eslint",
49
49
  "type-check": "tsc --noEmit"
50
50
  },
51
51
  "dependencies": {
52
- "@fastify/cors": "^11.2.0",
52
+ "@fastify/http-proxy": "^11.5.0",
53
53
  "@libsql/client": "^0.15.0",
54
54
  "@prisma/adapter-libsql": "^7.3.0",
55
55
  "@prisma/client": "^7.3.0",
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[105],{960:(e,t,a)=>{"use strict";a.d(t,{Jp:()=>s,Mu:()=>l,Pb:()=>c,XR:()=>o});let n="http://localhost:6251";async function r(e,t){let a=await fetch("".concat(n).concat(e),{headers:{"content-type":"application/json"},cache:"no-store",...t});if(!a.ok){let t="";try{t=JSON.stringify(await a.json())}catch(e){}throw Error("API ".concat(a.status," ").concat(e," ").concat(t))}if(204!==a.status)return a.json()}let l={list:function(){let e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];return r("/api/foods".concat(e?"?includeArchived=true":""))},create:e=>r("/api/foods",{method:"POST",body:JSON.stringify(e)}),update:(e,t)=>r("/api/foods/".concat(e),{method:"PATCH",body:JSON.stringify(t)})},s={list:e=>{let t=new URLSearchParams;(null==e?void 0:e.from)&&t.set("from",e.from),(null==e?void 0:e.to)&&t.set("to",e.to),(null==e?void 0:e.mealType)&&t.set("mealType",e.mealType);let a=t.toString();return r("/api/records".concat(a?"?".concat(a):""))},create:e=>r("/api/records",{method:"POST",body:JSON.stringify(e)}),createBatch:e=>r("/api/records/batch",{method:"POST",body:JSON.stringify({items:e})}),update:(e,t)=>r("/api/records/".concat(e),{method:"PATCH",body:JSON.stringify(t)}),remove:e=>r("/api/records/".concat(e),{method:"DELETE"})},c={list:()=>r("/api/goals"),current:()=>r("/api/goals/current"),create:e=>r("/api/goals",{method:"POST",body:JSON.stringify(e)}),remove:e=>r("/api/goals/".concat(e),{method:"DELETE"})},o={get:(e,t)=>{let a=new URLSearchParams({period:e});return t&&a.set("date",t),r("/api/summary?".concat(a.toString()))}}},3399:(e,t,a)=>{Promise.resolve().then(a.bind(a,4686))},4686:(e,t,a)=>{"use strict";a.d(t,{Dashboard:()=>y});var n=a(5155),r=a(2115),l=a(960),s=a(4804),c=a(5307),o=a(1594),i=a(4340),d=a(6449),u=a(4400),m=a(7882),h=a(251),x=a(6263);let g={top:12,right:12,bottom:28,left:44};function f(e){let t=new Date("".concat(e,"T00:00:00"));return"".concat(t.getMonth()+1,"/").concat(t.getDate())}function p(e){let{days:t}=e,[a,l]=function(){let e=(0,r.useRef)(null),[t,a]=(0,r.useState)(0);return(0,r.useEffect)(()=>{let t=e.current;if(!t)return;let n=new ResizeObserver(e=>{for(let t of e)a(t.contentRect.width)});return n.observe(t),()=>n.disconnect()},[]),[e,t]}(),{tooltipData:s,tooltipLeft:c,tooltipTop:p,tooltipOpen:b,showTooltip:j,hideTooltip:y}=(0,h.fS)(),N=Math.max(0,l-g.left-g.right),v=240-g.top-g.bottom,w=(0,r.useMemo)(()=>(0,u.WH)({domain:t.map(e=>e.date),range:[0,N],padding:.3}),[t,N]),S=(0,r.useMemo)(()=>100*Math.ceil(Math.max(1,...t.map(e=>{var t;return Math.max(e.total,null!=(t=e.target)?t:0)}))/100),[t]),k=(0,r.useMemo)(()=>(0,u.m4)({domain:[0,S],range:[v,0],nice:!0}),[S,v]),D=Math.max(1,Math.ceil(t.length/10));return(0,n.jsxs)("div",{ref:a,className:"relative w-full",children:[l>0&&(0,n.jsx)("svg",{width:l,height:240,children:(0,n.jsxs)(d.Y,{left:g.left,top:g.top,children:[(0,n.jsx)(i.YN,{scale:k,width:N,stroke:"#eee",numTicks:4}),t.map(e=>{var t;let a=null!=(t=w(e.date))?t:0,r=v-k(e.total),l=null!==e.target&&e.total>e.target;return(0,n.jsx)(m.yP,{x:a,y:k(e.total),width:w.bandwidth(),height:Math.max(0,r),rx:2,fill:l?"#ef4444":"#10b981",onMouseMove:t=>{var a,n;let r=(0,x.w)(t);j({tooltipData:e,tooltipLeft:(null!=(a=null==r?void 0:r.x)?a:0)+8,tooltipTop:(null!=(n=null==r?void 0:r.y)?n:0)-8})},onMouseLeave:y},e.date)}),(0,n.jsx)(m.Wi,{data:t.filter(e=>null!==e.target),x:e=>{var t;return(null!=(t=w(e.date))?t:0)+w.bandwidth()/2},y:e=>{var t;return k(null!=(t=e.target)?t:0)},stroke:"#737373",strokeWidth:1.5,strokeDasharray:"4 3"}),(0,n.jsx)(o.DZ,{scale:k,numTicks:4,stroke:"#ccc",tickStroke:"#ccc",tickLabelProps:()=>({fill:"#888",fontSize:10,dx:-2,dy:3})}),(0,n.jsx)(o.XI,{top:v,scale:w,stroke:"#ccc",tickStroke:"#ccc",tickFormat:e=>f(String(e)),tickValues:t.filter((e,t)=>t%D==0).map(e=>e.date),tickLabelProps:()=>({fill:"#888",fontSize:10,textAnchor:"middle"})})]})}),b&&s&&(0,n.jsxs)(h.du,{left:c,top:p,style:{...h.k5,fontSize:12},children:[(0,n.jsx)("div",{className:"font-medium",children:f(s.date)}),(0,n.jsxs)("div",{children:["摂取: ",s.total.toLocaleString()," kcal"]}),(0,n.jsxs)("div",{children:["目標:"," ",null!==s.target?"".concat(s.target.toLocaleString()," kcal"):"—"]})]}),(0,n.jsxs)("div",{className:"mt-2 flex gap-4 text-xs text-neutral-500",children:[(0,n.jsxs)("span",{className:"flex items-center gap-1",children:[(0,n.jsx)("span",{className:"inline-block h-2 w-2 rounded-sm bg-emerald-500"})," 目標内"]}),(0,n.jsxs)("span",{className:"flex items-center gap-1",children:[(0,n.jsx)("span",{className:"inline-block h-2 w-2 rounded-sm bg-red-500"})," 超過"]}),(0,n.jsxs)("span",{className:"flex items-center gap-1",children:[(0,n.jsx)("span",{className:"inline-block h-0 w-3 border-t border-dashed border-neutral-500"})," ","目標ライン"]})]})]})}let b={day:"日次",week:"週次",month:"月次"};function j(e,t,a){let n=new Date(e);return"day"===t?n.setDate(n.getDate()+a):"week"===t?n.setDate(n.getDate()+7*a):n.setMonth(n.getMonth()+a),n}function y(){var e;let[t,a]=(0,r.useState)("day"),[o,i]=(0,r.useState)(new Date),[d,u]=(0,r.useState)(null),[m,h]=(0,r.useState)(null),x=(0,r.useCallback)(async()=>{try{u(await l.XR.get(t,(0,s.JN)(o)))}catch(e){h(String(e))}},[t,o]);(0,r.useEffect)(()=>{x()},[x]);let g=d&&d.target?Math.min(100,Math.round(d.total/d.target*100)):null,f=(null==d?void 0:d.diff)!==null&&(null==d?void 0:d.diff)!==void 0&&d.diff>0;return(0,n.jsxs)("div",{className:"space-y-6",children:[m&&(0,n.jsx)("div",{className:"rounded-md bg-red-50 px-3 py-2 text-sm text-red-700",children:m}),(0,n.jsxs)("div",{className:"flex items-center justify-between",children:[(0,n.jsx)("div",{className:"flex gap-1",children:["day","week","month"].map(e=>(0,n.jsx)("button",{onClick:()=>a(e),className:"rounded-md px-3 py-1.5 text-sm ".concat(t===e?"bg-neutral-900 text-white":"bg-neutral-100 text-neutral-700"),children:b[e]},e))}),(0,n.jsxs)("div",{className:"flex items-center gap-1",children:[(0,n.jsx)("button",{onClick:()=>i(e=>j(e,t,-1)),className:"rounded-md bg-neutral-100 px-2 py-1.5 text-sm hover:bg-neutral-200",children:"←"}),(0,n.jsx)("button",{onClick:()=>i(new Date),className:"rounded-md bg-neutral-100 px-3 py-1.5 text-sm hover:bg-neutral-200",children:"今"}),(0,n.jsx)("button",{onClick:()=>i(e=>j(e,t,1)),className:"rounded-md bg-neutral-100 px-2 py-1.5 text-sm hover:bg-neutral-200",children:"→"})]})]}),d?(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)(c.Zp,{children:[(0,n.jsx)("div",{className:"mb-1 text-sm text-neutral-500",children:function(e){let t=new Date(e.from),a=new Date(e.to);if(a.setDate(a.getDate()-1),"day"===e.period)return t.toLocaleDateString("ja-JP",{dateStyle:"long"});let n=e=>e.toLocaleDateString("ja-JP",{month:"short",day:"numeric"});return"month"===e.period?t.toLocaleDateString("ja-JP",{year:"numeric",month:"long"}):"".concat(n(t)," 〜 ").concat(n(a))}(d)}),(0,n.jsxs)("div",{className:"flex items-end gap-3",children:[(0,n.jsx)("span",{className:"text-4xl font-bold",children:d.total.toLocaleString()}),(0,n.jsxs)("span",{className:"pb-1 text-sm text-neutral-500",children:["kcal"," ",null!==d.target?"/ 目標 ".concat(d.target.toLocaleString()," kcal"):"(目標未設定)"]})]}),null!==g&&(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)("div",{className:"mt-3 h-2.5 w-full overflow-hidden rounded-full bg-neutral-200",children:(0,n.jsx)("div",{className:"h-full ".concat(f?"bg-red-500":"bg-emerald-500"),style:{width:"".concat(g,"%")}})}),(0,n.jsxs)("div",{className:"mt-2 flex justify-between text-sm",children:[(0,n.jsxs)("span",{className:"text-neutral-500",children:["達成率 ",g,"%"]}),(0,n.jsxs)("span",{className:f?"text-red-600":"text-emerald-600",children:[f?"超過":"残り"," ",Math.abs(null!=(e=d.diff)?e:0).toLocaleString()," kcal"]})]})]})]}),"day"!==d.period&&(0,n.jsxs)(c.Zp,{children:[(0,n.jsx)(c._x,{children:"カロリー推移"}),(0,n.jsx)(p,{days:d.days})]}),(0,n.jsxs)(c.Zp,{children:[(0,n.jsx)(c._x,{children:"日別内訳"}),(0,n.jsx)("ul",{className:"divide-y divide-neutral-100 text-sm",children:d.days.map(e=>{let t=null!==e.target&&e.total>e.target;return(0,n.jsxs)("li",{className:"flex items-center justify-between py-1.5",children:[(0,n.jsx)("span",{className:"text-neutral-600",children:new Date("".concat(e.date,"T00:00:00")).toLocaleDateString("ja-JP",{month:"numeric",day:"numeric",weekday:"short"})}),(0,n.jsxs)("span",{className:"flex items-center gap-3 tabular-nums",children:[(0,n.jsxs)("span",{className:t?"font-medium text-red-600":"",children:[e.total.toLocaleString()," kcal"]}),(0,n.jsx)("span",{className:"w-24 text-right text-neutral-400",children:null!==e.target?"目標 ".concat(e.target.toLocaleString()):"—"})]})]},e.date)})})]})]}):(0,n.jsx)("p",{className:"text-sm text-neutral-500",children:"読み込み中…"})]})}},4804:(e,t,a)=>{"use strict";function n(e){let t=e.getFullYear(),a=String(e.getMonth()+1).padStart(2,"0"),n=String(e.getDate()).padStart(2,"0");return"".concat(t,"-").concat(a,"-").concat(n)}function r(e){let t=new Date(e);return t.setHours(0,0,0,0),t}function l(e){let t=r(e);return t.setDate(t.getDate()+1),t}function s(e){let t=e.getTimezoneOffset();return new Date(e.getTime()-6e4*t).toISOString().slice(0,16)}function c(e){let t=new Date(e);return"".concat(String(t.getHours()).padStart(2,"0"),":").concat(String(t.getMinutes()).padStart(2,"0"))}a.d(t,{D0:()=>l,JN:()=>n,fU:()=>c,iI:()=>s,ol:()=>r})},5307:(e,t,a)=>{"use strict";a.d(t,{$n:()=>s,D0:()=>i,Zp:()=>r,_x:()=>l,l6:()=>o,pd:()=>c});var n=a(5155);function r(e){let{children:t,className:a=""}=e;return(0,n.jsx)("div",{className:"rounded-xl border border-neutral-200 bg-white p-5 shadow-sm ".concat(a),children:t})}function l(e){let{children:t}=e;return(0,n.jsx)("h2",{className:"mb-3 text-base font-semibold text-neutral-800",children:t})}function s(e){let{children:t,variant:a="primary",className:r="",...l}=e;return(0,n.jsx)("button",{className:"rounded-md px-3 py-1.5 text-sm font-medium transition disabled:cursor-not-allowed ".concat({primary:"bg-neutral-900 text-white hover:bg-neutral-700 disabled:bg-neutral-400",ghost:"bg-neutral-100 text-neutral-700 hover:bg-neutral-200",danger:"text-red-600 hover:bg-red-50"}[a]," ").concat(r),...l,children:t})}function c(e){return(0,n.jsx)("input",{className:"w-full rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-sm outline-none focus:border-neutral-500",...e})}function o(e){return(0,n.jsx)("select",{className:"w-full rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-sm outline-none focus:border-neutral-500",...e})}function i(e){let{label:t,children:a}=e;return(0,n.jsxs)("label",{className:"block",children:[(0,n.jsx)("span",{className:"mb-1 block text-xs font-medium text-neutral-500",children:t}),a]})}}},e=>{e.O(0,[181,441,255,358],()=>e(e.s=3399)),_N_E=e.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[196],{960:(e,t,a)=>{"use strict";a.d(t,{Jp:()=>l,Mu:()=>s,Pb:()=>i,XR:()=>c});let n="http://localhost:6251";async function r(e,t){let a=await fetch("".concat(n).concat(e),{headers:{"content-type":"application/json"},cache:"no-store",...t});if(!a.ok){let t="";try{t=JSON.stringify(await a.json())}catch(e){}throw Error("API ".concat(a.status," ").concat(e," ").concat(t))}if(204!==a.status)return a.json()}let s={list:function(){let e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];return r("/api/foods".concat(e?"?includeArchived=true":""))},create:e=>r("/api/foods",{method:"POST",body:JSON.stringify(e)}),update:(e,t)=>r("/api/foods/".concat(e),{method:"PATCH",body:JSON.stringify(t)})},l={list:e=>{let t=new URLSearchParams;(null==e?void 0:e.from)&&t.set("from",e.from),(null==e?void 0:e.to)&&t.set("to",e.to),(null==e?void 0:e.mealType)&&t.set("mealType",e.mealType);let a=t.toString();return r("/api/records".concat(a?"?".concat(a):""))},create:e=>r("/api/records",{method:"POST",body:JSON.stringify(e)}),createBatch:e=>r("/api/records/batch",{method:"POST",body:JSON.stringify({items:e})}),update:(e,t)=>r("/api/records/".concat(e),{method:"PATCH",body:JSON.stringify(t)}),remove:e=>r("/api/records/".concat(e),{method:"DELETE"})},i={list:()=>r("/api/goals"),current:()=>r("/api/goals/current"),create:e=>r("/api/goals",{method:"POST",body:JSON.stringify(e)}),remove:e=>r("/api/goals/".concat(e),{method:"DELETE"})},c={get:(e,t)=>{let a=new URLSearchParams({period:e});return t&&a.set("date",t),r("/api/summary?".concat(a.toString()))}}},3950:(e,t,a)=>{"use strict";a.d(t,{FoodManager:()=>i});var n=a(5155),r=a(2115),s=a(960),l=a(5307);function i(){let[e,t]=(0,r.useState)([]),[a,i]=(0,r.useState)(!0),[c,o]=(0,r.useState)(null),[d,u]=(0,r.useState)(""),[m,h]=(0,r.useState)(""),[x,p]=(0,r.useState)(""),[b,f]=(0,r.useState)(!1);async function g(){t(await s.Mu.list(!0))}async function y(e){e.preventDefault(),f(!0),o(null);try{await s.Mu.create({name:d,caloriesPerServing:Number(m),unitLabel:x.trim()||void 0}),u(""),h(""),p(""),await g()}catch(e){o(String(e))}finally{f(!1)}}async function v(e){await s.Mu.update(e.id,{archived:!e.archived}),await g()}return(0,r.useEffect)(()=>{g().catch(e=>o(String(e))).finally(()=>i(!1))},[]),(0,n.jsxs)("div",{className:"space-y-6",children:[c&&(0,n.jsx)("div",{className:"rounded-md bg-red-50 px-3 py-2 text-sm text-red-700",children:c}),(0,n.jsxs)(l.Zp,{children:[(0,n.jsx)(l._x,{children:"食品を追加"}),(0,n.jsxs)("form",{onSubmit:y,className:"grid grid-cols-2 gap-3",children:[(0,n.jsx)("div",{className:"col-span-2",children:(0,n.jsx)(l.D0,{label:"食品名",children:(0,n.jsx)(l.pd,{value:d,onChange:e=>u(e.target.value),required:!0})})}),(0,n.jsx)(l.D0,{label:"カロリー (kcal)",children:(0,n.jsx)(l.pd,{type:"number",min:"0",value:m,onChange:e=>h(e.target.value),required:!0})}),(0,n.jsx)(l.D0,{label:"単位ラベル(任意・例: 1杯 / 100g)",children:(0,n.jsx)(l.pd,{value:x,onChange:e=>p(e.target.value)})}),(0,n.jsx)("div",{className:"col-span-2",children:(0,n.jsx)(l.$n,{type:"submit",disabled:b,children:b?"追加中…":"追加する"})})]})]}),(0,n.jsxs)(l.Zp,{children:[(0,n.jsx)(l._x,{children:"食品マスタ一覧"}),a?(0,n.jsx)("p",{className:"text-sm text-neutral-500",children:"読み込み中…"}):0===e.length?(0,n.jsx)("p",{className:"text-sm text-neutral-500",children:"まだ食品が登録されていません。"}):(0,n.jsx)("ul",{className:"divide-y divide-neutral-100",children:e.map(e=>(0,n.jsxs)("li",{className:"flex items-center justify-between py-2 text-sm",children:[(0,n.jsxs)("div",{className:e.archived?"text-neutral-400 line-through":"",children:[(0,n.jsx)("span",{className:"font-medium",children:e.name}),(0,n.jsxs)("span",{className:"ml-2 text-neutral-500",children:[e.caloriesPerServing," kcal",e.unitLabel?" / ".concat(e.unitLabel):""]})]}),(0,n.jsx)("div",{className:"flex items-center gap-2",children:(0,n.jsx)(l.$n,{variant:"ghost",onClick:()=>v(e),children:e.archived?"復活":"アーカイブ"})})]},e.id))})]})]})}},5307:(e,t,a)=>{"use strict";a.d(t,{$n:()=>l,D0:()=>o,Zp:()=>r,_x:()=>s,l6:()=>c,pd:()=>i});var n=a(5155);function r(e){let{children:t,className:a=""}=e;return(0,n.jsx)("div",{className:"rounded-xl border border-neutral-200 bg-white p-5 shadow-sm ".concat(a),children:t})}function s(e){let{children:t}=e;return(0,n.jsx)("h2",{className:"mb-3 text-base font-semibold text-neutral-800",children:t})}function l(e){let{children:t,variant:a="primary",className:r="",...s}=e;return(0,n.jsx)("button",{className:"rounded-md px-3 py-1.5 text-sm font-medium transition disabled:cursor-not-allowed ".concat({primary:"bg-neutral-900 text-white hover:bg-neutral-700 disabled:bg-neutral-400",ghost:"bg-neutral-100 text-neutral-700 hover:bg-neutral-200",danger:"text-red-600 hover:bg-red-50"}[a]," ").concat(r),...s,children:t})}function i(e){return(0,n.jsx)("input",{className:"w-full rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-sm outline-none focus:border-neutral-500",...e})}function c(e){return(0,n.jsx)("select",{className:"w-full rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-sm outline-none focus:border-neutral-500",...e})}function o(e){let{label:t,children:a}=e;return(0,n.jsxs)("label",{className:"block",children:[(0,n.jsx)("span",{className:"mb-1 block text-xs font-medium text-neutral-500",children:t}),a]})}},9049:(e,t,a)=>{Promise.resolve().then(a.bind(a,3950))}},e=>{e.O(0,[441,255,358],()=>e(e.s=9049)),_N_E=e.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[341],{960:(e,t,a)=>{"use strict";a.d(t,{Jp:()=>l,Mu:()=>s,Pb:()=>c,XR:()=>i});let n="http://localhost:6251";async function r(e,t){let a=await fetch("".concat(n).concat(e),{headers:{"content-type":"application/json"},cache:"no-store",...t});if(!a.ok){let t="";try{t=JSON.stringify(await a.json())}catch(e){}throw Error("API ".concat(a.status," ").concat(e," ").concat(t))}if(204!==a.status)return a.json()}let s={list:function(){let e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];return r("/api/foods".concat(e?"?includeArchived=true":""))},create:e=>r("/api/foods",{method:"POST",body:JSON.stringify(e)}),update:(e,t)=>r("/api/foods/".concat(e),{method:"PATCH",body:JSON.stringify(t)})},l={list:e=>{let t=new URLSearchParams;(null==e?void 0:e.from)&&t.set("from",e.from),(null==e?void 0:e.to)&&t.set("to",e.to),(null==e?void 0:e.mealType)&&t.set("mealType",e.mealType);let a=t.toString();return r("/api/records".concat(a?"?".concat(a):""))},create:e=>r("/api/records",{method:"POST",body:JSON.stringify(e)}),createBatch:e=>r("/api/records/batch",{method:"POST",body:JSON.stringify({items:e})}),update:(e,t)=>r("/api/records/".concat(e),{method:"PATCH",body:JSON.stringify(t)}),remove:e=>r("/api/records/".concat(e),{method:"DELETE"})},c={list:()=>r("/api/goals"),current:()=>r("/api/goals/current"),create:e=>r("/api/goals",{method:"POST",body:JSON.stringify(e)}),remove:e=>r("/api/goals/".concat(e),{method:"DELETE"})},i={get:(e,t)=>{let a=new URLSearchParams({period:e});return t&&a.set("date",t),r("/api/summary?".concat(a.toString()))}}},3143:(e,t,a)=>{Promise.resolve().then(a.bind(a,5913))},4804:(e,t,a)=>{"use strict";function n(e){let t=e.getFullYear(),a=String(e.getMonth()+1).padStart(2,"0"),n=String(e.getDate()).padStart(2,"0");return"".concat(t,"-").concat(a,"-").concat(n)}function r(e){let t=new Date(e);return t.setHours(0,0,0,0),t}function s(e){let t=r(e);return t.setDate(t.getDate()+1),t}function l(e){let t=e.getTimezoneOffset();return new Date(e.getTime()-6e4*t).toISOString().slice(0,16)}function c(e){let t=new Date(e);return"".concat(String(t.getHours()).padStart(2,"0"),":").concat(String(t.getMinutes()).padStart(2,"0"))}a.d(t,{D0:()=>s,JN:()=>n,fU:()=>c,iI:()=>l,ol:()=>r})},5307:(e,t,a)=>{"use strict";a.d(t,{$n:()=>l,D0:()=>o,Zp:()=>r,_x:()=>s,l6:()=>i,pd:()=>c});var n=a(5155);function r(e){let{children:t,className:a=""}=e;return(0,n.jsx)("div",{className:"rounded-xl border border-neutral-200 bg-white p-5 shadow-sm ".concat(a),children:t})}function s(e){let{children:t}=e;return(0,n.jsx)("h2",{className:"mb-3 text-base font-semibold text-neutral-800",children:t})}function l(e){let{children:t,variant:a="primary",className:r="",...s}=e;return(0,n.jsx)("button",{className:"rounded-md px-3 py-1.5 text-sm font-medium transition disabled:cursor-not-allowed ".concat({primary:"bg-neutral-900 text-white hover:bg-neutral-700 disabled:bg-neutral-400",ghost:"bg-neutral-100 text-neutral-700 hover:bg-neutral-200",danger:"text-red-600 hover:bg-red-50"}[a]," ").concat(r),...s,children:t})}function c(e){return(0,n.jsx)("input",{className:"w-full rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-sm outline-none focus:border-neutral-500",...e})}function i(e){return(0,n.jsx)("select",{className:"w-full rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-sm outline-none focus:border-neutral-500",...e})}function o(e){let{label:t,children:a}=e;return(0,n.jsxs)("label",{className:"block",children:[(0,n.jsx)("span",{className:"mb-1 block text-xs font-medium text-neutral-500",children:t}),a]})}},5913:(e,t,a)=>{"use strict";a.d(t,{GoalManager:()=>i});var n=a(5155),r=a(2115),s=a(960),l=a(4804),c=a(5307);function i(){let[e,t]=(0,r.useState)([]),[a,i]=(0,r.useState)(null),[o,d]=(0,r.useState)(!0),[u,m]=(0,r.useState)(null),[x,h]=(0,r.useState)(""),[p,f]=(0,r.useState)((0,l.JN)(new Date)),[g,b]=(0,r.useState)(!1);async function y(){let[e,a]=await Promise.all([s.Pb.list(),s.Pb.current()]);t(e),i(a)}async function j(e){e.preventDefault(),b(!0),m(null);try{await s.Pb.create({dailyTarget:Number(x),effectiveFrom:new Date("".concat(p,"T00:00:00")).toISOString()}),h(""),await y()}catch(e){m(String(e))}finally{b(!1)}}async function S(e){confirm("この目標(".concat(e.dailyTarget," kcal)を削除しますか?"))&&(await s.Pb.remove(e.id),await y())}return(0,r.useEffect)(()=>{y().catch(e=>m(String(e))).finally(()=>d(!1))},[]),(0,n.jsxs)("div",{className:"space-y-6",children:[u&&(0,n.jsx)("div",{className:"rounded-md bg-red-50 px-3 py-2 text-sm text-red-700",children:u}),(0,n.jsxs)(c.Zp,{children:[(0,n.jsx)(c._x,{children:"現在の目標"}),o?(0,n.jsx)("p",{className:"text-sm text-neutral-500",children:"読み込み中…"}):a?(0,n.jsxs)("div",{className:"flex items-end gap-2",children:[(0,n.jsx)("span",{className:"text-3xl font-bold",children:a.dailyTarget.toLocaleString()}),(0,n.jsxs)("span",{className:"text-sm text-neutral-500",children:["kcal / 日(",new Date(a.effectiveFrom).toLocaleDateString("ja-JP")," 〜 適用中)"]})]}):(0,n.jsx)("p",{className:"text-sm text-neutral-500",children:"まだ目標が設定されていません。"})]}),(0,n.jsxs)(c.Zp,{children:[(0,n.jsx)(c._x,{children:"目標を設定 / 変更"}),(0,n.jsxs)("form",{onSubmit:j,className:"grid grid-cols-2 gap-3",children:[(0,n.jsx)(c.D0,{label:"1日の目標カロリー (kcal)",children:(0,n.jsx)(c.pd,{type:"number",min:"0",value:x,onChange:e=>h(e.target.value),placeholder:"1500",required:!0})}),(0,n.jsx)(c.D0,{label:"適用開始日",children:(0,n.jsx)(c.pd,{type:"date",value:p,onChange:e=>f(e.target.value)})}),(0,n.jsxs)("div",{className:"col-span-2",children:[(0,n.jsx)(c.$n,{type:"submit",disabled:g,children:g?"保存中…":"この目標を保存"}),(0,n.jsx)("p",{className:"mt-2 text-xs text-neutral-500",children:"※ 適用開始日ごとに履歴が残り、各日はその日時点の目標で評価されます。"})]})]})]}),(0,n.jsxs)(c.Zp,{children:[(0,n.jsx)(c._x,{children:"目標の履歴"}),0===e.length?(0,n.jsx)("p",{className:"text-sm text-neutral-500",children:"履歴はありません。"}):(0,n.jsx)("ul",{className:"divide-y divide-neutral-100",children:e.map(e=>(0,n.jsxs)("li",{className:"flex items-center justify-between py-2 text-sm",children:[(0,n.jsxs)("div",{children:[(0,n.jsxs)("span",{className:"font-medium",children:[e.dailyTarget.toLocaleString()," kcal/日"]}),(0,n.jsxs)("span",{className:"ml-2 text-neutral-500",children:[new Date(e.effectiveFrom).toLocaleDateString("ja-JP")," 〜"]}),(null==a?void 0:a.id)===e.id&&(0,n.jsx)("span",{className:"ml-2 rounded bg-emerald-100 px-1.5 py-0.5 text-xs text-emerald-700",children:"適用中"})]}),(0,n.jsx)(c.$n,{variant:"danger",onClick:()=>S(e),children:"削除"})]},e.id))})]})]})}}},e=>{e.O(0,[441,255,358],()=>e(e.s=3143)),_N_E=e.O()}]);