Products: create services from the admin

- "New service": from scratch (category, who does it — cleaner, driver, career
  consultant or docs agent —, fixed price and duration, online, name and
  description in 5 languages) or as a copy of an existing service (prices,
  questions, checklist, booking window and question texts copied). Created
  switched off; delete only without orders; reorder within a category.
- "Questions in the order form" builder for fixed-price services: choice,
  number, text, yes/no, required flag; labels stored as text overrides.
- The admin itself now loads text overrides, so new service names show right
  away; the mini app hides missing optional texts (description, option hints)
  instead of showing keys.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
zabudko-ruandClaude Opus 5.5 committed 2026-10-21 03:32:17 +04:00
1 parent 0edc6432b7
commit 8866616782
8 files changed
+599 -13

No files matched your search

@@ -1,7 +1,7 @@
import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import type { ParamField, ParamValues } from '@barev/core';
import { Button, Card, Center, Chip, Field, Icon, Spinner, Stepper } from '@barev/ui';
import { dateKeyToDate, errorText, haptic, navigate, nextDays, useI18n, useLocation, useToast, useTRPC } from '@barev/web';
import { tOptional, dateKeyToDate, errorText, haptic, navigate, nextDays, useI18n, useLocation, useToast, useTRPC } from '@barev/web';
import { useEffect, useMemo, useState } from 'react';
import { PageHead } from '../lib.tsx';
@@ -127,7 +127,7 @@ export function OrderForm({ code }: { code: string }) {
<main className="app app--plain">
<PageHead title={t(`services.${code}.name`)} />
<p className="bc-muted" style={{ margin: '-4px 0 0' }}>
{t(`services.${code}.description`)}
{tOptional(t, `services.${code}.description`)}
</p>
{/* параметры услуги */}
@@ -335,7 +335,7 @@ function ParamInput({
<button key={o} type="button" className="option" role="radio" aria-checked={value === o} aria-pressed={value === o} onClick={() => onChange(o)}>
<span style={{ fontWeight: 700 }}>{t(`params.${serviceCode}.${field.key}.${o}`)}</span>
<span className="bc-small bc-muted">
{prices?.[o] !== undefined ? t('common.from', { price: amd(prices[o]!) }) : t(`params.${serviceCode}.${field.key}.${o}_hint`)}
{prices?.[o] !== undefined ? t('common.from', { price: amd(prices[o]!) }) : tOptional(t, `params.${serviceCode}.${field.key}.${o}_hint`)}
</span>
</button>
))}
@@ -1,6 +1,6 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Button, Card, Field, Icon } from '@barev/ui';
import { errorText, haptic, useI18n, useToast, useTRPC } from '@barev/web';
import { tOptional, errorText, haptic, useI18n, useToast, useTRPC } from '@barev/web';
import { useState } from 'react';
import { levelDiscount, PageHead, toneClass, type Level } from '../lib.tsx';
@@ -31,7 +31,7 @@ export function ServiceRequest({ code }: { code: string }) {
<main className="app app--plain">
<PageHead title={t(`services.${code}.name`)} />
<Card className={`bc-stack ${category ? toneClass(category.code) : ''}`}>
<p style={{ margin: 0 }}>{t(`services.${code}.description`)}</p>
<p style={{ margin: 0 }}>{tOptional(t, `services.${code}.description`)}</p>
{pct > 0 && <div style={{ fontWeight: 700 }}>{t('client.request.yourDiscount', { pct })}</div>}
</Card>
{sent ? (