|
@@ -1,36 +1,10 @@
|
|
|
-import { useEffect, useMemo, useState } from 'react'
|
|
|
|
|
|
|
+import { useMemo } from 'react'
|
|
|
import { Link } from 'react-router-dom'
|
|
import { Link } from 'react-router-dom'
|
|
|
import { Helmet } from 'react-helmet-async'
|
|
import { Helmet } from 'react-helmet-async'
|
|
|
-import { getCustomerDashboardSummary } from '../../services/api/customers'
|
|
|
|
|
-import { getTemplateDashboardSummary } from '../../services/api/templates'
|
|
|
|
|
-import { getStylistMe } from '../../services/api/stylist'
|
|
|
|
|
-import { getWorkbenchData } from '../../services/api/workbench'
|
|
|
|
|
-import type { CustomerDashboardSummary, TemplateDashboardSummary } from '../../services/api/contracts'
|
|
|
|
|
-import type { WorkbenchEntity } from '../../services/api/contracts'
|
|
|
|
|
|
|
+import { getSessionIdentity } from '../../services/http/session'
|
|
|
import { usePageContext } from '../../shared/pageContext'
|
|
import { usePageContext } from '../../shared/pageContext'
|
|
|
import { DashboardLayout } from '../../layouts/DashboardLayout'
|
|
import { DashboardLayout } from '../../layouts/DashboardLayout'
|
|
|
|
|
|
|
|
-const emptyWorkbench: WorkbenchEntity = {
|
|
|
|
|
- appointments: [],
|
|
|
|
|
- followUps: [],
|
|
|
|
|
- records: [],
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-const emptyCustomerSummary: CustomerDashboardSummary = {
|
|
|
|
|
- total: 0,
|
|
|
|
|
- today: 0,
|
|
|
|
|
- followUp: 0,
|
|
|
|
|
- simulated: 0,
|
|
|
|
|
- weeklyRecords: 0,
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-const emptyTemplateSummary: TemplateDashboardSummary = {
|
|
|
|
|
- total: 0,
|
|
|
|
|
- published: 0,
|
|
|
|
|
- weeklyUsed: 0,
|
|
|
|
|
- draft: 0,
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
function greetingForHour(hour: number, locale: string): string {
|
|
function greetingForHour(hour: number, locale: string): string {
|
|
|
if (locale === 'ja') {
|
|
if (locale === 'ja') {
|
|
|
if (hour < 12) return 'おはようございます'
|
|
if (hour < 12) return 'おはようございます'
|
|
@@ -44,18 +18,21 @@ function greetingForHour(hour: number, locale: string): string {
|
|
|
|
|
|
|
|
export function DashboardPage() {
|
|
export function DashboardPage() {
|
|
|
const { locale, path, copy } = usePageContext()
|
|
const { locale, path, copy } = usePageContext()
|
|
|
- const [dashboardData, setDashboardData] = useState<WorkbenchEntity>(emptyWorkbench)
|
|
|
|
|
- const [customerSummary, setCustomerSummary] = useState<CustomerDashboardSummary>(emptyCustomerSummary)
|
|
|
|
|
- const [templateSummary, setTemplateSummary] = useState<TemplateDashboardSummary>(emptyTemplateSummary)
|
|
|
|
|
- const [stylistName, setStylistName] = useState('')
|
|
|
|
|
const pageCopy = copy.dashboardPage
|
|
const pageCopy = copy.dashboardPage
|
|
|
-
|
|
|
|
|
- useEffect(() => {
|
|
|
|
|
- getWorkbenchData().then(setDashboardData).catch(() => setDashboardData(emptyWorkbench))
|
|
|
|
|
- getCustomerDashboardSummary().then(setCustomerSummary).catch(() => setCustomerSummary(emptyCustomerSummary))
|
|
|
|
|
- getTemplateDashboardSummary().then(setTemplateSummary).catch(() => setTemplateSummary(emptyTemplateSummary))
|
|
|
|
|
- getStylistMe().then((me) => setStylistName(me.name || me.displayName)).catch(() => setStylistName(''))
|
|
|
|
|
- }, [])
|
|
|
|
|
|
|
+ const sessionIdentity = useMemo(() => getSessionIdentity(), [])
|
|
|
|
|
+ const stylistName = sessionIdentity.username || pageCopy.greetingName
|
|
|
|
|
+ const undevelopedCopy = pageCopy.undeveloped
|
|
|
|
|
+ const undevelopedStats = [
|
|
|
|
|
+ pageCopy.stats.todayCustomers,
|
|
|
|
|
+ pageCopy.stats.followUp,
|
|
|
|
|
+ pageCopy.stats.weeklyRecords,
|
|
|
|
|
+ pageCopy.stats.templates,
|
|
|
|
|
+ ]
|
|
|
|
|
+ const undevelopedPanels = [
|
|
|
|
|
+ { layoutClassName: 'col-xl-7', ...pageCopy.panels.today },
|
|
|
|
|
+ { layoutClassName: 'col-xl-5', ...pageCopy.panels.followUp },
|
|
|
|
|
+ { layoutClassName: 'col-12', ...pageCopy.panels.records },
|
|
|
|
|
+ ]
|
|
|
|
|
|
|
|
const now = useMemo(() => new Date(), [])
|
|
const now = useMemo(() => new Date(), [])
|
|
|
const dateLabel = useMemo(
|
|
const dateLabel = useMemo(
|
|
@@ -68,9 +45,6 @@ export function DashboardPage() {
|
|
|
[locale, now],
|
|
[locale, now],
|
|
|
)
|
|
)
|
|
|
const greeting = greetingForHour(now.getHours(), locale)
|
|
const greeting = greetingForHour(now.getHours(), locale)
|
|
|
- const headlineStat = locale === 'ja'
|
|
|
|
|
- ? `本日の予約 ${customerSummary.today} 件 / 未対応フォロー ${customerSummary.followUp} 件`
|
|
|
|
|
- : `今日预约 ${customerSummary.today} 件 / 待跟进 ${customerSummary.followUp} 件`
|
|
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<DashboardLayout>
|
|
<DashboardLayout>
|
|
@@ -84,12 +58,12 @@ export function DashboardPage() {
|
|
|
<div className="row align-items-end justify-content-between mb-4">
|
|
<div className="row align-items-end justify-content-between mb-4">
|
|
|
<div className="col-lg-10">
|
|
<div className="col-lg-10">
|
|
|
<span className="bg-base-color d-inline-block mb-15px fw-600 text-white text-uppercase border-radius-30px ps-20px pe-20px fs-12">{pageCopy.badge}</span>
|
|
<span className="bg-base-color d-inline-block mb-15px fw-600 text-white text-uppercase border-radius-30px ps-20px pe-20px fs-12">{pageCopy.badge}</span>
|
|
|
- <h1 className="fw-700 text-dark-gray ls-minus-1px mb-10px">{greeting}<br />{stylistName || pageCopy.greetingName}</h1>
|
|
|
|
|
|
|
+ <h1 className="fw-700 text-dark-gray ls-minus-1px mb-10px">{greeting}<br />{stylistName}</h1>
|
|
|
<p className="fs-18 lh-30 mb-0">{pageCopy.summary}</p>
|
|
<p className="fs-18 lh-30 mb-0">{pageCopy.summary}</p>
|
|
|
</div>
|
|
</div>
|
|
|
<div className="col-lg-4 text-lg-end mt-3 mt-lg-0">
|
|
<div className="col-lg-4 text-lg-end mt-3 mt-lg-0">
|
|
|
<span className="d-block fs-14 text-medium-gray">{dateLabel}</span>
|
|
<span className="d-block fs-14 text-medium-gray">{dateLabel}</span>
|
|
|
- <span className="fw-700 text-dark-gray">{headlineStat}</span>
|
|
|
|
|
|
|
+ {/* 当前版本未开发欢迎区统计,后续从 workbench 聚合接口恢复预约与跟进真实数据。 */}
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -118,10 +92,16 @@ export function DashboardPage() {
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div className="row row-cols-2 row-cols-lg-4 row-cols-md-4 g-3 mb-5">
|
|
<div className="row row-cols-2 row-cols-lg-4 row-cols-md-4 g-3 mb-5">
|
|
|
- <div className="col"><div className="workbench-stat bg-white border-radius-8px p-20px"><span className="d-block fs-26 fw-800 text-dark-gray">{customerSummary.today}</span><span className="fs-14">{pageCopy.stats.todayCustomers}</span></div></div>
|
|
|
|
|
- <div className="col"><div className="workbench-stat bg-white border-radius-8px p-20px"><span className="d-block fs-26 fw-800 text-dark-gray">{customerSummary.followUp}</span><span className="fs-14">{pageCopy.stats.followUp}</span></div></div>
|
|
|
|
|
- <div className="col"><div className="workbench-stat bg-white border-radius-8px p-20px"><span className="d-block fs-26 fw-800 text-dark-gray">{customerSummary.weeklyRecords}</span><span className="fs-14">{pageCopy.stats.weeklyRecords}</span></div></div>
|
|
|
|
|
- <div className="col"><div className="workbench-stat bg-white border-radius-8px p-20px"><span className="d-block fs-26 fw-800 text-dark-gray">{templateSummary.total}</span><span className="fs-14">{pageCopy.stats.templates}</span></div></div>
|
|
|
|
|
|
|
+ {/* 当前版本未开发统计卡,后续从顾客汇总、模板汇总和工作台聚合接口恢复真实数据。 */}
|
|
|
|
|
+ {undevelopedStats.map((label) => (
|
|
|
|
|
+ <div key={label} className="col">
|
|
|
|
|
+ <div className="workbench-stat bg-white border-radius-8px p-20px opacity-75">
|
|
|
|
|
+ <span className="d-block fs-26 fw-800 text-dark-gray">{undevelopedCopy.statValue}</span>
|
|
|
|
|
+ <span className="fs-14 d-block">{label}</span>
|
|
|
|
|
+ <span className="fs-13 text-medium-gray d-block mt-2">{undevelopedCopy.badge}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ))}
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</section>
|
|
</section>
|
|
@@ -129,73 +109,27 @@ export function DashboardPage() {
|
|
|
<section className="pt-0">
|
|
<section className="pt-0">
|
|
|
<div className="container">
|
|
<div className="container">
|
|
|
<div className="row g-4">
|
|
<div className="row g-4">
|
|
|
- <div className="col-xl-7">
|
|
|
|
|
- <div id="today" className="workbench-panel bg-white border-radius-8px box-shadow-extra-large p-30px h-100">
|
|
|
|
|
- <div className="d-flex align-items-center justify-content-between mb-25px">
|
|
|
|
|
- <div>
|
|
|
|
|
- <span className="fs-14 fw-700 text-base-color text-uppercase">{pageCopy.panels.today.badge}</span>
|
|
|
|
|
- <h2 className="fw-700 text-dark-gray fs-26 mb-0">{pageCopy.panels.today.title}</h2>
|
|
|
|
|
- </div>
|
|
|
|
|
- <Link to={path('/customers')} className="btn btn-small btn-rounded btn-transparent-light-gray border border-1 border-color-dark-gray fw-700">{pageCopy.panels.today.action}</Link>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div className="workbench-list">
|
|
|
|
|
- {dashboardData.appointments.length === 0 ? (
|
|
|
|
|
- <p className="fs-15 text-medium-gray mb-0">{copy.common.empty}</p>
|
|
|
|
|
- ) : dashboardData.appointments.map((item, index) => (
|
|
|
|
|
- <div key={`${item.time}-${item.customer}`} className="workbench-list-item">
|
|
|
|
|
- <div><span className="fw-700 text-dark-gray d-block">{item.time} {item.customer}</span><span className="fs-14 text-medium-gray">{item.note}</span></div>
|
|
|
|
|
- <Link to={path('/simulation')} className={`btn btn-very-small btn-rounded ${index === 0 ? 'btn-base-color' : 'btn-transparent-light-gray border border-1 border-color-dark-gray'}`}>{index === 0 ? pageCopy.panels.today.start : pageCopy.panels.today.detail}</Link>
|
|
|
|
|
- </div>
|
|
|
|
|
- ))}
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div className="col-xl-5">
|
|
|
|
|
- <div id="follow-up" className="workbench-panel bg-white border-radius-8px box-shadow-extra-large p-30px h-100">
|
|
|
|
|
- <div className="d-flex align-items-center justify-content-between mb-25px">
|
|
|
|
|
- <div>
|
|
|
|
|
- <span className="fs-14 fw-700 text-base-color text-uppercase">{pageCopy.panels.followUp.badge}</span>
|
|
|
|
|
- <h2 className="fw-700 text-dark-gray fs-26 mb-0">{pageCopy.panels.followUp.title}</h2>
|
|
|
|
|
- </div>
|
|
|
|
|
- <Link to={path('/customers')} className="btn btn-small btn-rounded btn-transparent-light-gray border border-1 border-color-dark-gray fw-700">{pageCopy.panels.followUp.action}</Link>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div className="workbench-list compact">
|
|
|
|
|
- {dashboardData.followUps.length === 0 ? (
|
|
|
|
|
- <p className="fs-15 text-medium-gray mb-0">{copy.common.empty}</p>
|
|
|
|
|
- ) : dashboardData.followUps.map((item) => (
|
|
|
|
|
- <div key={`${item.customer}-${item.reason}`} className="workbench-list-item">
|
|
|
|
|
- <div><span className="fw-700 text-dark-gray d-block">{item.customer}</span><span className="fs-14 text-medium-gray">{item.reason}</span></div>
|
|
|
|
|
- <span className="workbench-badge">{item.badge}</span>
|
|
|
|
|
|
|
+ {/* 当前版本未开发工作台列表区块,后续从 workbench 聚合接口恢复今日顾客、待跟进顾客和最近施术记录。 */}
|
|
|
|
|
+ {undevelopedPanels.map((panel) => (
|
|
|
|
|
+ <div key={panel.title} className={panel.layoutClassName}>
|
|
|
|
|
+ <div className="workbench-panel bg-white border-radius-8px box-shadow-extra-large p-30px h-100 opacity-75">
|
|
|
|
|
+ <div className="d-flex align-items-center justify-content-between mb-25px">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <span className="fs-14 fw-700 text-base-color text-uppercase">{panel.badge}</span>
|
|
|
|
|
+ <h2 className="fw-700 text-dark-gray fs-26 mb-0">{panel.title}</h2>
|
|
|
</div>
|
|
</div>
|
|
|
- ))}
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div className="col-12">
|
|
|
|
|
- <div id="records" className="workbench-panel bg-white border-radius-8px box-shadow-extra-large p-30px">
|
|
|
|
|
- <div className="d-flex align-items-center justify-content-between mb-25px">
|
|
|
|
|
- <div>
|
|
|
|
|
- <span className="fs-14 fw-700 text-base-color text-uppercase">{pageCopy.panels.records.badge}</span>
|
|
|
|
|
- <h2 className="fw-700 text-dark-gray fs-26 mb-0">{pageCopy.panels.records.title}</h2>
|
|
|
|
|
- </div>
|
|
|
|
|
- <Link to={path('/customers')} className="btn btn-small btn-rounded btn-transparent-light-gray border border-1 border-color-dark-gray fw-700">{pageCopy.panels.records.action}</Link>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div className="workbench-record-table">
|
|
|
|
|
- <div className="workbench-record-row workbench-record-head">
|
|
|
|
|
- {pageCopy.panels.records.headers.map((label: string) => <span key={label}>{label}</span>)}
|
|
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ className="btn btn-small btn-rounded btn-transparent-light-gray border border-1 border-color-dark-gray fw-700"
|
|
|
|
|
+ disabled
|
|
|
|
|
+ >
|
|
|
|
|
+ {undevelopedCopy.disabledAction}
|
|
|
|
|
+ </button>
|
|
|
</div>
|
|
</div>
|
|
|
- {dashboardData.records.length === 0 ? (
|
|
|
|
|
- <p className="fs-15 text-medium-gray mb-0 mt-3">{copy.common.empty}</p>
|
|
|
|
|
- ) : dashboardData.records.map((item) => (
|
|
|
|
|
- <div key={`${item.date}-${item.customer}`} className="workbench-record-row">
|
|
|
|
|
- <span>{item.date}</span><span>{item.customer}</span><span>{item.menu}</span><span>{item.template || '—'}</span><span>{item.follow || '—'}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- ))}
|
|
|
|
|
|
|
+ <p className="fs-15 text-medium-gray mb-0">{undevelopedCopy.body}</p>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
|
+ ))}
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</section>
|
|
</section>
|