Compare commits
No commits in common. "7ac18ec61db0bc82d43e91e8a50f3106562994f7" and "b126f2c52d5f6e27edab5550cd3b1cb42a2c005f" have entirely different histories.
7ac18ec61d
...
b126f2c52d
@ -354,9 +354,6 @@ func leadDraft(it storage.ConfirmItem, cfg storage.BookingSettings) bitrix.LeadD
|
|||||||
"Приём: " + formatSlotTime(it.Start),
|
"Приём: " + formatSlotTime(it.Start),
|
||||||
"Филиал: " + html.EscapeString(branchOrDash(it.Branch)),
|
"Филиал: " + html.EscapeString(branchOrDash(it.Branch)),
|
||||||
}
|
}
|
||||||
if it.Comment != "" {
|
|
||||||
lines = append(lines, "Комментарий пациента: "+html.EscapeString(it.Comment))
|
|
||||||
}
|
|
||||||
if it.ServiceName != "" {
|
if it.ServiceName != "" {
|
||||||
lines = append(lines, "Услуга: "+html.EscapeString(it.ServiceName))
|
lines = append(lines, "Услуга: "+html.EscapeString(it.ServiceName))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,7 +52,6 @@ func (s *Storage) SetBookingSettings(ctx context.Context, b BookingSettings) err
|
|||||||
|
|
||||||
// ConfirmItem — запись с сайта, ждущая решения оператора, со всем, что нужно для лида в Битриксе и CRM.
|
// ConfirmItem — запись с сайта, ждущая решения оператора, со всем, что нужно для лида в Битриксе и CRM.
|
||||||
type ConfirmItem struct {
|
type ConfirmItem struct {
|
||||||
Comment string
|
|
||||||
ID int64
|
ID int64
|
||||||
Start time.Time
|
Start time.Time
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
@ -74,7 +73,7 @@ const confirmItemSelect = `
|
|||||||
SELECT a.id, a.start_at, a.created_at, a.patient_name, a.patient_phone, a.doctor_onec_id,
|
SELECT a.id, a.start_at, a.created_at, a.patient_name, a.patient_phone, a.doctor_onec_id,
|
||||||
COALESCE(NULLIF(o.full_name, ''), a.doctor_onec_id), COALESCE(NULLIF(b.address, ''), b.name, ''),
|
COALESCE(NULLIF(o.full_name, ''), a.doctor_onec_id), COALESCE(NULLIF(b.address, ''), b.name, ''),
|
||||||
COALESCE(sv.name, ''), a.onec_appointment_id, a.bitrix_lead_id, a.error,
|
COALESCE(sv.name, ''), a.onec_appointment_id, a.bitrix_lead_id, a.error,
|
||||||
a.crm_deal_id, a.crm_confirm_state, a.crm_error, a.patient_comment
|
a.crm_deal_id, a.crm_confirm_state, a.crm_error
|
||||||
FROM onec_appointments a
|
FROM onec_appointments a
|
||||||
LEFT JOIN onec_doctors o ON o.onec_id = a.doctor_onec_id
|
LEFT JOIN onec_doctors o ON o.onec_id = a.doctor_onec_id
|
||||||
LEFT JOIN onec_branches b ON b.onec_id = a.branch_onec_id
|
LEFT JOIN onec_branches b ON b.onec_id = a.branch_onec_id
|
||||||
@ -134,7 +133,7 @@ func (s *Storage) confirmItems(ctx context.Context, query string, limit int) ([]
|
|||||||
var c ConfirmItem
|
var c ConfirmItem
|
||||||
if err := rows.Scan(&c.ID, &c.Start, &c.CreatedAt, &c.Patient, &c.Phone, &c.DoctorOnec, &c.DoctorName,
|
if err := rows.Scan(&c.ID, &c.Start, &c.CreatedAt, &c.Patient, &c.Phone, &c.DoctorOnec, &c.DoctorName,
|
||||||
&c.Branch, &c.ServiceName, &c.OnecID, &c.BitrixLeadID, &c.Error,
|
&c.Branch, &c.ServiceName, &c.OnecID, &c.BitrixLeadID, &c.Error,
|
||||||
&c.CrmDealID, &c.CrmConfirmState, &c.CrmError, &c.Comment); err != nil {
|
&c.CrmDealID, &c.CrmConfirmState, &c.CrmError); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
out = append(out, c)
|
out = append(out, c)
|
||||||
|
|||||||
@ -10,6 +10,27 @@
|
|||||||
color: rgba(44, 73, 95, 0.35);
|
color: rgba(44, 73, 95, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rating-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 5.2px;
|
||||||
|
height: 29px;
|
||||||
|
padding: 5.9px 8.2px 5.9px 7.4px;
|
||||||
|
background: var(--soft);
|
||||||
|
border: 0.744px solid var(--chip-border);
|
||||||
|
border-radius: 8.923px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 13.385px;
|
||||||
|
color: var(--navy);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rating-badge img {
|
||||||
|
width: 13.385px;
|
||||||
|
height: 13.385px;
|
||||||
|
padding: 0 0.33px 1.28px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- плитка «Специалисты» на главной: 209 × 232 ---------- */
|
/* ---------- плитка «Специалисты» на главной: 209 × 232 ---------- */
|
||||||
.doctor-tile {
|
.doctor-tile {
|
||||||
width: 209px;
|
width: 209px;
|
||||||
@ -35,6 +56,12 @@
|
|||||||
object-position: center top;
|
object-position: center top;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.doctor-tile__rating {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
.doctor-tile__info {
|
.doctor-tile__info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -192,6 +219,22 @@
|
|||||||
height: 180px;
|
height: 180px;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
.doctor-tile__rating {
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
height: auto;
|
||||||
|
padding: 2px 8px;
|
||||||
|
gap: 4px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: var(--r-8);
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 15px;
|
||||||
|
}
|
||||||
|
.doctor-tile__rating img {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
.doctor-tile__info {
|
.doctor-tile__info {
|
||||||
gap: 0;
|
gap: 0;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { experienceLabel } from '../lib/format.js';
|
import starIcon from '../assets/figma/icon-star.svg';
|
||||||
|
import { experienceLabel, ratingLabel } from '../lib/format.js';
|
||||||
import { doctorPath } from '../lib/links.js';
|
import { doctorPath } from '../lib/links.js';
|
||||||
import { useLeadModal } from '../context/LeadModalContext.jsx';
|
import { useLeadModal } from '../context/LeadModalContext.jsx';
|
||||||
import './DoctorCard.css';
|
import './DoctorCard.css';
|
||||||
@ -40,6 +41,16 @@ function DoctorPhoto({ doctor, className }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function RatingBadge({ rating, className = '' }) {
|
||||||
|
if (typeof rating !== 'number') return null;
|
||||||
|
return (
|
||||||
|
<span className={`rating-badge ${className}`} aria-label={`Рейтинг ${ratingLabel(rating)}`}>
|
||||||
|
<img src={starIcon} alt="" width="13" height="13" />
|
||||||
|
{ratingLabel(rating)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Плитка для слайдера «Специалисты» на главной.
|
// Плитка для слайдера «Специалисты» на главной.
|
||||||
export function DoctorTile({ doctor }) {
|
export function DoctorTile({ doctor }) {
|
||||||
const path = doctorPath(doctor);
|
const path = doctorPath(doctor);
|
||||||
@ -47,6 +58,7 @@ export function DoctorTile({ doctor }) {
|
|||||||
<article className="doctor-tile">
|
<article className="doctor-tile">
|
||||||
<Link to={path} className="doctor-tile__photo-link" tabIndex={-1} aria-hidden="true">
|
<Link to={path} className="doctor-tile__photo-link" tabIndex={-1} aria-hidden="true">
|
||||||
<DoctorPhoto doctor={doctor} className="doctor-tile__photo" />
|
<DoctorPhoto doctor={doctor} className="doctor-tile__photo" />
|
||||||
|
<RatingBadge rating={doctor.rating} className="doctor-tile__rating" />
|
||||||
</Link>
|
</Link>
|
||||||
<div className="doctor-tile__info">
|
<div className="doctor-tile__info">
|
||||||
<Link to={path} className="doctor-tile__name">
|
<Link to={path} className="doctor-tile__name">
|
||||||
@ -78,6 +90,7 @@ export function DoctorRow({ doctor }) {
|
|||||||
{doctor.experience_years != null && (
|
{doctor.experience_years != null && (
|
||||||
<span className="doctor-row__exp">{experienceLabel(doctor.experience_years)}</span>
|
<span className="doctor-row__exp">{experienceLabel(doctor.experience_years)}</span>
|
||||||
)}
|
)}
|
||||||
|
<RatingBadge rating={doctor.rating} />
|
||||||
</div>
|
</div>
|
||||||
<div className="doctor-row__actions">
|
<div className="doctor-row__actions">
|
||||||
{doctor.absent ? (
|
{doctor.absent ? (
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Link, useParams } from 'react-router-dom';
|
import { Link, useParams } from 'react-router-dom';
|
||||||
import { BookButton } from '../components/DoctorCard.jsx';
|
import { BookButton, RatingBadge } from '../components/DoctorCard.jsx';
|
||||||
import { NextSlots } from '../components/NativeBooking.jsx';
|
import { NextSlots } from '../components/NativeBooking.jsx';
|
||||||
import ContactsSection from '../components/ContactsSection.jsx';
|
import ContactsSection from '../components/ContactsSection.jsx';
|
||||||
import StubPage from './StubPage.jsx';
|
import StubPage from './StubPage.jsx';
|
||||||
@ -177,6 +177,7 @@ export default function DoctorPage() {
|
|||||||
{d.experience_years != null && (
|
{d.experience_years != null && (
|
||||||
<span className="doctor-hero__exp">{experienceLabel(d.experience_years)}</span>
|
<span className="doctor-hero__exp">{experienceLabel(d.experience_years)}</span>
|
||||||
)}
|
)}
|
||||||
|
<RatingBadge rating={d.rating} />
|
||||||
</div>
|
</div>
|
||||||
{d.branches?.length > 0 && (
|
{d.branches?.length > 0 && (
|
||||||
<p className="doctor-hero__branches">
|
<p className="doctor-hero__branches">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user