// MyBookings — 飼い主のマイページ。予約履歴・再予約・初回カウンセリング・レビュー投稿を集約
const MY_BOOKINGS = [
{
id:'B-3842', status:'completed', sitter:'みかこ', sitterId:1, sitterPhoto:'peach',
date:'2026-05-12(日)', time:'14:00–16:00', service:'訪問ケア', pet:'ココ(柴犬・3歳)',
total: 12900, reviewed:true, rating:5,
},
{
id:'B-3744', status:'completed', sitter:'みかこ', sitterId:1, sitterPhoto:'peach',
date:'2026-04-28(日)', time:'10:00–12:00', service:'訪問ケア・散歩', pet:'ココ(柴犬・3歳)',
total: 12900, reviewed:false, rating:null,
},
{
id:'B-3611', status:'completed', sitter:'たくみ', sitterId:2, sitterPhoto:'warm',
date:'2026-04-06(日)', time:'09:00–13:00', service:'訪問ケア・散歩', pet:'ココ(柴犬・3歳)',
total: 21600, reviewed:true, rating:5,
},
{
id:'B-3520', status:'completed', sitter:'ゆり', sitterId:3, sitterPhoto:'sage',
date:'2026-03-24(日)', time:'13:00–15:00', service:'訪問ケア', pet:'ココ(柴犬・3歳)',
total: 13800, reviewed:true, rating:4,
},
];
const UPCOMING = [
{
id:'B-3911', status:'upcoming', sitter:'みかこ', sitterId:1, sitterPhoto:'peach',
date:'2026-05-24(土)', time:'10:00–13:00', service:'訪問ケア・散歩', pet:'ココ(柴犬・3歳)',
total: 16100, paymentStatus:'paid', // pre-paid
},
];
function MyBookings({ initialTab = 'overview' }) {
const [tab, setTab] = React.useState(initialTab); // overview | history | counseling | reviews
const [reviewing, setReviewing] = React.useState(null); // booking to review
return (
{/* Header */}
{/* Sub-nav tabs */}
{[
['overview','ホーム'],
['history','予約履歴'],
['counseling','初回カウンセリング'],
['reviews','レビュー'],
].map(([k,n])=>(
))}
{tab === 'overview' && }
{tab === 'history' && }
{tab === 'counseling' && }
{tab === 'reviews' && }
{reviewing &&
setReviewing(null)}/>}
);
}
// ---------- ホーム ----------
function OverviewTab({ onTab, onReview }) {
const next = UPCOMING[0];
const unreviewed = MY_BOOKINGS.filter(b => !b.reviewed);
return (
{/* 次の予定 */}
予約を編集 : null}/>
{next ? (
{next.date} ・ {next.time}
{next.sitter}さんが {next.pet} のお世話
{next.service}
) : (
予約はありません。シッターを探してみましょう。
)}
{/* 未投稿のレビュー */}
{unreviewed.length > 0 && (
onTab('reviews')}>すべて見る}/>
{unreviewed.slice(0,2).map(b=>(
onReview(b)}/>
))}
)}
{/* リピート予約 */}
onTab('history')}>履歴を見る}/>
以前と同じシッター・同じ条件で、ワンクリックで予約できます。
{MY_BOOKINGS.slice(0,2).map(b => )}
{/* 初回カウンセリング */}
初回カウンセリング
30分・無料。ペットの性格や暮らしについてヒアリングし、相性のいいシッターをご提案します。
{/* サマリー */}
);
}
// ---------- 予約履歴 ----------
function HistoryTab({ onReview }) {
const [filter, setFilter] = React.useState('all'); // all | upcoming | completed | unreviewed
const all = [...UPCOMING.map(b=>({...b,status:'upcoming'})), ...MY_BOOKINGS];
const items = all.filter(b => {
if (filter === 'all') return true;
if (filter === 'upcoming') return b.status === 'upcoming';
if (filter === 'completed') return b.status === 'completed';
if (filter === 'unreviewed') return b.status === 'completed' && !b.reviewed;
return true;
});
return (
{[['all','すべて'],['upcoming','予定'],['completed','完了'],['unreviewed','未レビュー']].map(([k,n])=>(
))}
{items.map(b => (
))}
);
}
function BookingRow({ booking, onReview }) {
const b = booking;
const isUpcoming = b.status === 'upcoming';
return (
{isUpcoming?'予定':'完了'}
{b.id}
{b.date}・{b.time}
{b.sitter}さん ・ {b.service}
{b.pet} ・ 合計 ¥{b.total.toLocaleString()}(事前精算済)
{b.status === 'completed' && b.reviewed && (
あなたのレビュー:{b.rating}.0
)}
{isUpcoming ? (
<>
>
) : (
<>
{!b.reviewed && (
)}
>
)}
);
}
// ---------- 初回カウンセリング ----------
function CounselingTab() {
const slots = [
['5/22 (木)','10:00','available'],
['5/22 (木)','14:00','available'],
['5/22 (木)','19:00','full'],
['5/23 (金)','11:00','available'],
['5/23 (金)','15:00','available'],
['5/23 (金)','20:00','available'],
['5/24 (土)','10:00','full'],
['5/24 (土)','13:00','available'],
['5/24 (土)','16:00','available'],
];
const [pick, setPick] = React.useState('5/23 (金) 15:00');
return (
初めての方限定 ・ 30分 ・ 無料
まずはお話を聞かせてください
専属のコンシェルジュが、ペットの性格や暮らし方を伺ったうえで、相性の良いシッターをご提案します。
ビデオ通話または電話、どちらでもOKです。
{slots.map(([d,t,st],i)=>{
const key = `${d} ${t}`;
const sel = pick === key;
const full = st === 'full';
return (
);
})}
{['ビデオ通話','電話'].map((n,i)=>(
))}
);
}
// ---------- レビュー ----------
function ReviewsTab({ onReview }) {
const unreviewed = MY_BOOKINGS.filter(b => !b.reviewed);
const myReviews = MY_BOOKINGS.filter(b => b.reviewed);
return (
未投稿のレビュー
{unreviewed.length === 0 ? (
未投稿のレビューはありません。
) : (
{unreviewed.map(b => onReview(b)}/>)}
)}
投稿済みのレビュー
{myReviews.map(b => (
{[1,2,3,4,5].map(i=>(
)).slice(0, b.rating)}
とても丁寧に対応していただきました。写真もたくさん送ってくれて、安心して任せられます。
))}
);
}
// ---------- レビュー入力モーダル ----------
function ReviewModal({ booking, onClose }) {
const [rating, setRating] = React.useState(5);
const [tags, setTags] = React.useState([]);
const [text, setText] = React.useState('');
const tagOptions = ['丁寧','時間通り','報告が細かい','うちの子が懐いた','清潔','また頼みたい'];
const toggleTag = (t) => setTags(s => s.includes(t) ? s.filter(x=>x!==t) : [...s,t]);
return (
e.stopPropagation()} style={{
width:'min(560px, 100%)',maxHeight:'90%',overflow:'auto',
background:'#fff',borderRadius:24,padding:'28px 28px 24px',
}}>
REVIEW
{booking.sitter}さんへのレビュー
{booking.date}・{booking.service}
満足度を教えてください
{[1,2,3,4,5].map(i=>(
))}
{['','要改善','物足りない','ふつう','満足','とても満足'][rating]}
{tagOptions.map(t=>{
const sel = tags.includes(t);
return (
);
})}
);
}
// ---------- 共通パーツ ----------
function Card({ children, style }) {
return (
{children}
);
}
function CardHead({ label, right }) {
return (
);
}
function StatBox({n,l}) {
return (
);
}
function PaidBadge() {
return (
支払い完了
);
}
function ReviewPrompt({ booking, onClick }) {
return (
{booking.sitter}さんはいかがでしたか?
{booking.date}・{booking.service}
);
}
function RebookCard({booking}) {
const b = booking;
return (
前回:{b.date}
{b.sitter}さん
{b.service} ・ ¥{b.total.toLocaleString()}
);
}
const Field = ({label, hint, children, half}) => (
{label}
{children}
{hint &&
{hint}
}
);
const inputCss = {
width:'100%',padding:'12px 14px',borderRadius:12,
border:'1.5px solid rgba(0,0,0,.08)',background:'#fff',
fontSize:14,fontFamily:'inherit',color:'#2E2418',outline:'none',
};
window.MyBookings = MyBookings;