From ce6ea377ce55c6f965fdf6fcbdb0ffd054424a70 Mon Sep 17 00:00:00 2001 From: Egor Pozharov Date: Thu, 16 Apr 2026 20:16:21 +0600 Subject: [PATCH] add customer name, service info, second pickup location, and structured address fields to deliveries --- .../000003_add_delivery_fields.down.sql | 10 + .../000003_add_delivery_fields.up.sql | 18 ++ backend/internal/db/queries/query.sql | 29 +- backend/internal/db/sqlc/models.go | 9 + backend/internal/db/sqlc/query.sql.go | 98 ++++++- backend/internal/delivery/handler.go | 55 +++- frontend/src/api/deliveries.ts | 40 ++- .../src/components/delivery/DeliveryCard.tsx | 45 ++- .../src/components/delivery/DeliveryForm.tsx | 179 +++++++++++- .../src/components/delivery/DeliveryList.tsx | 1 + .../src/components/delivery/DeliveryRow.tsx | 20 +- frontend/src/pages/Dashboard.tsx | 26 +- frontend/src/types/index.ts | 12 +- frontend/src/utils/addressParser.ts | 122 ++++++++ frontend/vite.config.ts | 2 +- frontend/yarn.lock | 263 ++++++++++++++++-- 16 files changed, 852 insertions(+), 77 deletions(-) create mode 100644 backend/internal/db/migrations/000003_add_delivery_fields.down.sql create mode 100644 backend/internal/db/migrations/000003_add_delivery_fields.up.sql create mode 100644 frontend/src/utils/addressParser.ts diff --git a/backend/internal/db/migrations/000003_add_delivery_fields.down.sql b/backend/internal/db/migrations/000003_add_delivery_fields.down.sql new file mode 100644 index 0000000..5d7efeb --- /dev/null +++ b/backend/internal/db/migrations/000003_add_delivery_fields.down.sql @@ -0,0 +1,10 @@ +-- Revert new fields for delivery improvements +ALTER TABLE deliveries DROP COLUMN IF EXISTS customer_name; +ALTER TABLE deliveries DROP COLUMN IF EXISTS service_info; +ALTER TABLE deliveries DROP COLUMN IF EXISTS pickup_location_2; +ALTER TABLE deliveries DROP COLUMN IF EXISTS product_name_2; +ALTER TABLE deliveries DROP COLUMN IF EXISTS street; +ALTER TABLE deliveries DROP COLUMN IF EXISTS house; +ALTER TABLE deliveries DROP COLUMN IF EXISTS apartment; +ALTER TABLE deliveries DROP COLUMN IF EXISTS entrance; +ALTER TABLE deliveries DROP COLUMN IF EXISTS floor; diff --git a/backend/internal/db/migrations/000003_add_delivery_fields.up.sql b/backend/internal/db/migrations/000003_add_delivery_fields.up.sql new file mode 100644 index 0000000..ccdadeb --- /dev/null +++ b/backend/internal/db/migrations/000003_add_delivery_fields.up.sql @@ -0,0 +1,18 @@ +-- Add new fields for delivery improvements + +-- Client information +ALTER TABLE deliveries ADD COLUMN customer_name text NOT NULL DEFAULT ''; + +-- Services (assembly, lifting, etc.) +ALTER TABLE deliveries ADD COLUMN service_info text; + +-- Second pickup location +ALTER TABLE deliveries ADD COLUMN pickup_location_2 varchar(20); +ALTER TABLE deliveries ADD COLUMN product_name_2 text; + +-- Structured address components +ALTER TABLE deliveries ADD COLUMN street text NOT NULL DEFAULT ''; +ALTER TABLE deliveries ADD COLUMN house text NOT NULL DEFAULT ''; +ALTER TABLE deliveries ADD COLUMN apartment text; +ALTER TABLE deliveries ADD COLUMN entrance text; +ALTER TABLE deliveries ADD COLUMN floor text; diff --git a/backend/internal/db/queries/query.sql b/backend/internal/db/queries/query.sql index 3330a15..d95b3b3 100644 --- a/backend/internal/db/queries/query.sql +++ b/backend/internal/db/queries/query.sql @@ -10,8 +10,12 @@ SELECT * FROM users WHERE username = $1; SELECT * FROM deliveries WHERE date = $1; -- name: CreateDelivery :one -INSERT INTO deliveries (date, pickup_location, product_name, address, phone, additional_phone, has_elevator, comment) -VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +INSERT INTO deliveries ( + date, pickup_location, pickup_location_2, product_name, product_name_2, + customer_name, address, street, house, apartment, entrance, floor, + phone, additional_phone, has_elevator, service_info, comment +) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) RETURNING *; -- name: GetDeliveryByID :one @@ -21,7 +25,26 @@ SELECT * FROM deliveries WHERE id = $1; DELETE FROM deliveries WHERE id = $1; -- name: UpdateDelivery :exec -UPDATE deliveries SET date = $1, pickup_location = $2, product_name = $3, address = $4, phone = $5, additional_phone = $6, has_elevator = $7, comment = $8, updated_at = NOW() WHERE id = $9; +UPDATE deliveries SET + date = $1, + pickup_location = $2, + pickup_location_2 = $3, + product_name = $4, + product_name_2 = $5, + customer_name = $6, + address = $7, + street = $8, + house = $9, + apartment = $10, + entrance = $11, + floor = $12, + phone = $13, + additional_phone = $14, + has_elevator = $15, + service_info = $16, + comment = $17, + updated_at = NOW() +WHERE id = $18; -- name: GetDeliveryCount :many SELECT COUNT(*) as count, date FROM deliveries diff --git a/backend/internal/db/sqlc/models.go b/backend/internal/db/sqlc/models.go index ec79662..ecd3eae 100644 --- a/backend/internal/db/sqlc/models.go +++ b/backend/internal/db/sqlc/models.go @@ -21,6 +21,15 @@ type Delivery struct { Status string `db:"status" json:"status"` CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + CustomerName string `db:"customer_name" json:"customer_name"` + ServiceInfo pgtype.Text `db:"service_info" json:"service_info"` + PickupLocation2 pgtype.Text `db:"pickup_location_2" json:"pickup_location_2"` + ProductName2 pgtype.Text `db:"product_name_2" json:"product_name_2"` + Street string `db:"street" json:"street"` + House string `db:"house" json:"house"` + Apartment pgtype.Text `db:"apartment" json:"apartment"` + Entrance pgtype.Text `db:"entrance" json:"entrance"` + Floor pgtype.Text `db:"floor" json:"floor"` } type User struct { diff --git a/backend/internal/db/sqlc/query.sql.go b/backend/internal/db/sqlc/query.sql.go index cb4c81d..dcf453f 100644 --- a/backend/internal/db/sqlc/query.sql.go +++ b/backend/internal/db/sqlc/query.sql.go @@ -12,19 +12,32 @@ import ( ) const createDelivery = `-- name: CreateDelivery :one -INSERT INTO deliveries (date, pickup_location, product_name, address, phone, additional_phone, has_elevator, comment) -VALUES ($1, $2, $3, $4, $5, $6, $7, $8) -RETURNING id, date, pickup_location, product_name, address, phone, additional_phone, has_elevator, comment, status, created_at, updated_at +INSERT INTO deliveries ( + date, pickup_location, pickup_location_2, product_name, product_name_2, + customer_name, address, street, house, apartment, entrance, floor, + phone, additional_phone, has_elevator, service_info, comment +) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) +RETURNING id, date, pickup_location, product_name, address, phone, additional_phone, has_elevator, comment, status, created_at, updated_at, customer_name, service_info, pickup_location_2, product_name_2, street, house, apartment, entrance, floor ` type CreateDeliveryParams struct { Date pgtype.Date `db:"date" json:"date"` PickupLocation string `db:"pickup_location" json:"pickup_location"` + PickupLocation2 pgtype.Text `db:"pickup_location_2" json:"pickup_location_2"` ProductName string `db:"product_name" json:"product_name"` + ProductName2 pgtype.Text `db:"product_name_2" json:"product_name_2"` + CustomerName string `db:"customer_name" json:"customer_name"` Address string `db:"address" json:"address"` + Street string `db:"street" json:"street"` + House string `db:"house" json:"house"` + Apartment pgtype.Text `db:"apartment" json:"apartment"` + Entrance pgtype.Text `db:"entrance" json:"entrance"` + Floor pgtype.Text `db:"floor" json:"floor"` Phone string `db:"phone" json:"phone"` AdditionalPhone pgtype.Text `db:"additional_phone" json:"additional_phone"` HasElevator bool `db:"has_elevator" json:"has_elevator"` + ServiceInfo pgtype.Text `db:"service_info" json:"service_info"` Comment pgtype.Text `db:"comment" json:"comment"` } @@ -32,11 +45,20 @@ func (q *Queries) CreateDelivery(ctx context.Context, arg CreateDeliveryParams) row := q.db.QueryRow(ctx, createDelivery, arg.Date, arg.PickupLocation, + arg.PickupLocation2, arg.ProductName, + arg.ProductName2, + arg.CustomerName, arg.Address, + arg.Street, + arg.House, + arg.Apartment, + arg.Entrance, + arg.Floor, arg.Phone, arg.AdditionalPhone, arg.HasElevator, + arg.ServiceInfo, arg.Comment, ) var i Delivery @@ -53,6 +75,15 @@ func (q *Queries) CreateDelivery(ctx context.Context, arg CreateDeliveryParams) &i.Status, &i.CreatedAt, &i.UpdatedAt, + &i.CustomerName, + &i.ServiceInfo, + &i.PickupLocation2, + &i.ProductName2, + &i.Street, + &i.House, + &i.Apartment, + &i.Entrance, + &i.Floor, ) return i, err } @@ -90,7 +121,7 @@ func (q *Queries) DeleteDelivery(ctx context.Context, id pgtype.UUID) error { } const getDeliveriesByDate = `-- name: GetDeliveriesByDate :many -SELECT id, date, pickup_location, product_name, address, phone, additional_phone, has_elevator, comment, status, created_at, updated_at FROM deliveries WHERE date = $1 +SELECT id, date, pickup_location, product_name, address, phone, additional_phone, has_elevator, comment, status, created_at, updated_at, customer_name, service_info, pickup_location_2, product_name_2, street, house, apartment, entrance, floor FROM deliveries WHERE date = $1 ` func (q *Queries) GetDeliveriesByDate(ctx context.Context, date pgtype.Date) ([]Delivery, error) { @@ -115,6 +146,15 @@ func (q *Queries) GetDeliveriesByDate(ctx context.Context, date pgtype.Date) ([] &i.Status, &i.CreatedAt, &i.UpdatedAt, + &i.CustomerName, + &i.ServiceInfo, + &i.PickupLocation2, + &i.ProductName2, + &i.Street, + &i.House, + &i.Apartment, + &i.Entrance, + &i.Floor, ); err != nil { return nil, err } @@ -127,7 +167,7 @@ func (q *Queries) GetDeliveriesByDate(ctx context.Context, date pgtype.Date) ([] } const getDeliveryByID = `-- name: GetDeliveryByID :one -SELECT id, date, pickup_location, product_name, address, phone, additional_phone, has_elevator, comment, status, created_at, updated_at FROM deliveries WHERE id = $1 +SELECT id, date, pickup_location, product_name, address, phone, additional_phone, has_elevator, comment, status, created_at, updated_at, customer_name, service_info, pickup_location_2, product_name_2, street, house, apartment, entrance, floor FROM deliveries WHERE id = $1 ` func (q *Queries) GetDeliveryByID(ctx context.Context, id pgtype.UUID) (Delivery, error) { @@ -146,6 +186,15 @@ func (q *Queries) GetDeliveryByID(ctx context.Context, id pgtype.UUID) (Delivery &i.Status, &i.CreatedAt, &i.UpdatedAt, + &i.CustomerName, + &i.ServiceInfo, + &i.PickupLocation2, + &i.ProductName2, + &i.Street, + &i.House, + &i.Apartment, + &i.Entrance, + &i.Floor, ) return i, err } @@ -198,17 +247,45 @@ func (q *Queries) GetUserByUsername(ctx context.Context, username string) (User, } const updateDelivery = `-- name: UpdateDelivery :exec -UPDATE deliveries SET date = $1, pickup_location = $2, product_name = $3, address = $4, phone = $5, additional_phone = $6, has_elevator = $7, comment = $8, updated_at = NOW() WHERE id = $9 +UPDATE deliveries SET + date = $1, + pickup_location = $2, + pickup_location_2 = $3, + product_name = $4, + product_name_2 = $5, + customer_name = $6, + address = $7, + street = $8, + house = $9, + apartment = $10, + entrance = $11, + floor = $12, + phone = $13, + additional_phone = $14, + has_elevator = $15, + service_info = $16, + comment = $17, + updated_at = NOW() +WHERE id = $18 ` type UpdateDeliveryParams struct { Date pgtype.Date `db:"date" json:"date"` PickupLocation string `db:"pickup_location" json:"pickup_location"` + PickupLocation2 pgtype.Text `db:"pickup_location_2" json:"pickup_location_2"` ProductName string `db:"product_name" json:"product_name"` + ProductName2 pgtype.Text `db:"product_name_2" json:"product_name_2"` + CustomerName string `db:"customer_name" json:"customer_name"` Address string `db:"address" json:"address"` + Street string `db:"street" json:"street"` + House string `db:"house" json:"house"` + Apartment pgtype.Text `db:"apartment" json:"apartment"` + Entrance pgtype.Text `db:"entrance" json:"entrance"` + Floor pgtype.Text `db:"floor" json:"floor"` Phone string `db:"phone" json:"phone"` AdditionalPhone pgtype.Text `db:"additional_phone" json:"additional_phone"` HasElevator bool `db:"has_elevator" json:"has_elevator"` + ServiceInfo pgtype.Text `db:"service_info" json:"service_info"` Comment pgtype.Text `db:"comment" json:"comment"` ID pgtype.UUID `db:"id" json:"id"` } @@ -217,11 +294,20 @@ func (q *Queries) UpdateDelivery(ctx context.Context, arg UpdateDeliveryParams) _, err := q.db.Exec(ctx, updateDelivery, arg.Date, arg.PickupLocation, + arg.PickupLocation2, arg.ProductName, + arg.ProductName2, + arg.CustomerName, arg.Address, + arg.Street, + arg.House, + arg.Apartment, + arg.Entrance, + arg.Floor, arg.Phone, arg.AdditionalPhone, arg.HasElevator, + arg.ServiceInfo, arg.Comment, arg.ID, ) diff --git a/backend/internal/delivery/handler.go b/backend/internal/delivery/handler.go index 9377d62..cf5ce30 100644 --- a/backend/internal/delivery/handler.go +++ b/backend/internal/delivery/handler.go @@ -16,14 +16,23 @@ type Handler struct { // DeliveryRequest represents the request body for creating or updating a delivery type DeliveryRequest struct { - Date string `json:"date" binding:"required"` // DD-MM-YYYY - PickupLocation string `json:"pickup_location" binding:"required,oneof=warehouse symbat nursaya galaktika"` - ProductName string `json:"product_name" binding:"required"` - Address string `json:"address" binding:"required"` - Phone string `json:"phone" binding:"required"` - AdditionalPhone string `json:"additional_phone"` - HasElevator bool `json:"has_elevator"` - Comment string `json:"comment"` + Date string `json:"date" binding:"required"` // DD-MM-YYYY + PickupLocation string `json:"pickup_location" binding:"required,oneof=warehouse symbat nursaya galaktika"` + PickupLocation2 *string `json:"pickup_location_2" binding:"omitempty,oneof=warehouse symbat nursaya galaktika"` + ProductName string `json:"product_name" binding:"required"` + ProductName2 *string `json:"product_name_2"` + CustomerName string `json:"customer_name" binding:"required"` + Address string `json:"address" binding:"required"` + Street string `json:"street" binding:"required"` + House string `json:"house" binding:"required"` + Apartment *string `json:"apartment"` + Entrance *string `json:"entrance"` + Floor *string `json:"floor"` + Phone string `json:"phone" binding:"required"` + AdditionalPhone *string `json:"additional_phone"` + HasElevator bool `json:"has_elevator"` + ServiceInfo *string `json:"service_info"` + Comment string `json:"comment"` } func NewHandler(queries *sqlc.Queries) *Handler { @@ -99,11 +108,20 @@ func (h *Handler) CreateDelivery(c *gin.Context) { params := sqlc.CreateDeliveryParams{ Date: pgtype.Date{Time: t, Valid: true}, PickupLocation: req.PickupLocation, + PickupLocation2: pgtype.Text{String: derefString(req.PickupLocation2), Valid: req.PickupLocation2 != nil}, ProductName: req.ProductName, + ProductName2: pgtype.Text{String: derefString(req.ProductName2), Valid: req.ProductName2 != nil}, + CustomerName: req.CustomerName, Address: req.Address, + Street: req.Street, + House: req.House, + Apartment: pgtype.Text{String: derefString(req.Apartment), Valid: req.Apartment != nil}, + Entrance: pgtype.Text{String: derefString(req.Entrance), Valid: req.Entrance != nil}, + Floor: pgtype.Text{String: derefString(req.Floor), Valid: req.Floor != nil}, Phone: req.Phone, - AdditionalPhone: pgtype.Text{String: req.AdditionalPhone, Valid: req.AdditionalPhone != ""}, + AdditionalPhone: pgtype.Text{String: derefString(req.AdditionalPhone), Valid: req.AdditionalPhone != nil}, HasElevator: req.HasElevator, + ServiceInfo: pgtype.Text{String: derefString(req.ServiceInfo), Valid: req.ServiceInfo != nil}, Comment: pgtype.Text{String: req.Comment, Valid: true}, } res, err := h.queries.CreateDelivery(c.Request.Context(), params) @@ -146,11 +164,20 @@ func (h *Handler) UpdateDelivery(c *gin.Context) { ID: pgtype.UUID{Bytes: parsedID, Valid: true}, Date: pgtype.Date{Time: t, Valid: true}, PickupLocation: req.PickupLocation, + PickupLocation2: pgtype.Text{String: derefString(req.PickupLocation2), Valid: req.PickupLocation2 != nil}, ProductName: req.ProductName, + ProductName2: pgtype.Text{String: derefString(req.ProductName2), Valid: req.ProductName2 != nil}, + CustomerName: req.CustomerName, Address: req.Address, + Street: req.Street, + House: req.House, + Apartment: pgtype.Text{String: derefString(req.Apartment), Valid: req.Apartment != nil}, + Entrance: pgtype.Text{String: derefString(req.Entrance), Valid: req.Entrance != nil}, + Floor: pgtype.Text{String: derefString(req.Floor), Valid: req.Floor != nil}, Phone: req.Phone, - AdditionalPhone: pgtype.Text{String: req.AdditionalPhone, Valid: req.AdditionalPhone != ""}, + AdditionalPhone: pgtype.Text{String: derefString(req.AdditionalPhone), Valid: req.AdditionalPhone != nil}, HasElevator: req.HasElevator, + ServiceInfo: pgtype.Text{String: derefString(req.ServiceInfo), Valid: req.ServiceInfo != nil}, Comment: pgtype.Text{String: req.Comment, Valid: true}, }); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update delivery", "details": err.Error()}) @@ -231,3 +258,11 @@ func parseDate(dateStr string) (time.Time, error) { } return t, nil } + +// derefString safely dereferences a string pointer +func derefString(s *string) string { + if s == nil { + return "" + } + return *s +} diff --git a/frontend/src/api/deliveries.ts b/frontend/src/api/deliveries.ts index b996956..ac4bc41 100644 --- a/frontend/src/api/deliveries.ts +++ b/frontend/src/api/deliveries.ts @@ -7,11 +7,20 @@ interface BackendDelivery { id: string; date: string; // YYYY-MM-DD from pgtype.Date pickup_location: PickupLocation; + pickup_location_2: PickupLocation | null; product_name: string; + product_name_2: string | null; + customer_name: string; address: string; + street: string; + house: string; + apartment: string | null; + entrance: string | null; + floor: string | null; phone: string; additional_phone: string | null; has_elevator: boolean; + service_info: string | null; comment: string; status: DeliveryStatus; created_at: string; // ISO timestamp @@ -51,11 +60,20 @@ function mapBackendToFrontend(backend: BackendDelivery): Delivery { id: backend.id, date: backendDateToFrontend(backend.date), pickupLocation: backend.pickup_location, + pickupLocation2: backend.pickup_location_2 || undefined, productName: backend.product_name, + productName2: backend.product_name_2 || undefined, + customerName: backend.customer_name, address: backend.address, + street: backend.street, + house: backend.house, + apartment: backend.apartment || undefined, + entrance: backend.entrance || undefined, + floor: backend.floor || undefined, phone: backend.phone, additionalPhone: backend.additional_phone || undefined, hasElevator: backend.has_elevator, + serviceInfo: backend.service_info || undefined, comment: backend.comment, status: backend.status, createdAt: new Date(backend.created_at).getTime(), @@ -96,11 +114,20 @@ export const deliveriesApi = { const payload = { date: data.date, pickup_location: data.pickupLocation, + pickup_location_2: data.pickupLocation2 || null, product_name: data.productName, + product_name_2: data.productName2 || null, + customer_name: data.customerName, address: data.address, + street: data.street, + house: data.house, + apartment: data.apartment || null, + entrance: data.entrance || null, + floor: data.floor || null, phone: data.phone, - additional_phone: data.additionalPhone || '', + additional_phone: data.additionalPhone || null, has_elevator: data.hasElevator, + service_info: data.serviceInfo || null, comment: data.comment, }; const response = await api.post('/api/deliveries', payload); @@ -115,11 +142,20 @@ export const deliveriesApi = { const payload = { date: data.date, pickup_location: data.pickupLocation, + pickup_location_2: data.pickupLocation2 || null, product_name: data.productName, + product_name_2: data.productName2 || null, + customer_name: data.customerName, address: data.address, + street: data.street, + house: data.house, + apartment: data.apartment || null, + entrance: data.entrance || null, + floor: data.floor || null, phone: data.phone, - additional_phone: data.additionalPhone || '', + additional_phone: data.additionalPhone || null, has_elevator: data.hasElevator, + service_info: data.serviceInfo || null, comment: data.comment, }; await api.patch(`/api/deliveries/${id}`, payload); diff --git a/frontend/src/components/delivery/DeliveryCard.tsx b/frontend/src/components/delivery/DeliveryCard.tsx index d772fbb..7b69141 100644 --- a/frontend/src/components/delivery/DeliveryCard.tsx +++ b/frontend/src/components/delivery/DeliveryCard.tsx @@ -1,10 +1,12 @@ import { memo } from 'react'; -import { MapPin, Phone, Package, Store, Calendar, MessageSquare, CheckCircle2, Circle, CheckSquare } from 'lucide-react'; +import { MapPin, Phone, Package, Store, Calendar, MessageSquare, CheckCircle2, Circle, CheckSquare, User, Wrench } from 'lucide-react'; import type { Delivery } from '../../types'; import { pickupLocationLabels } from '../../types'; import { StatusBadge } from './StatusBadge'; import { Card } from '../ui/Card'; +const CITY = 'kokshetau'; + interface DeliveryCardProps { delivery: Delivery; onStatusChange: (id: string) => void; @@ -15,7 +17,7 @@ interface DeliveryCardProps { export const DeliveryCard = memo(({ delivery, onStatusChange, onEdit, onDelete }: DeliveryCardProps) => { const handleAddressClick = () => { const encodedAddress = encodeURIComponent(delivery.address); - window.open(`https://maps.google.com/?q=${encodedAddress}`, '_blank'); + window.open(`https://2gis.kz/${CITY}/search/${encodedAddress}`, '_blank'); }; const handlePhoneClick = () => { @@ -56,10 +58,20 @@ export const DeliveryCard = memo(({ delivery, onStatusChange, onEdit, onDelete } {delivery.date} + {/* Pickup locations */}
- {pickupLocationLabels[delivery.pickupLocation]} + + {delivery.pickupLocation2 + ? `${pickupLocationLabels[delivery.pickupLocation]} + ${pickupLocationLabels[delivery.pickupLocation2]}` + : pickupLocationLabels[delivery.pickupLocation]} +
+ {delivery.pickupLocation2 && delivery.productName2 && ( +
+ Со 2-й точки: {delivery.productName2} +
+ )}
@@ -71,11 +83,25 @@ export const DeliveryCard = memo(({ delivery, onStatusChange, onEdit, onDelete } className="flex items-start gap-2 text-sm w-full text-left hover:bg-[#f5f3f5] -mx-1 px-1 py-0.5 rounded transition-colors" > - - {delivery.address} - +
+ + ул. {delivery.street}, д. {delivery.house}{delivery.apartment ? `, кв. ${delivery.apartment}` : ''} + + {(delivery.entrance || delivery.floor) && ( + + {delivery.entrance && `Подъезд ${delivery.entrance}`} + {delivery.entrance && delivery.floor && ', '} + {delivery.floor && `этаж ${delivery.floor}`} + + )} +
+
+ + {delivery.customerName} +
+
+ {delivery.serviceInfo && ( +
+ + {delivery.serviceInfo} +
+ )} + {delivery.comment && (
diff --git a/frontend/src/components/delivery/DeliveryForm.tsx b/frontend/src/components/delivery/DeliveryForm.tsx index b9ad7c2..32c6b07 100644 --- a/frontend/src/components/delivery/DeliveryForm.tsx +++ b/frontend/src/components/delivery/DeliveryForm.tsx @@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'react'; import { Button, Input, Select, Modal } from '../ui'; import { pickupOptions } from '../../constants/pickup'; import { formatDateForInput, parseDateFromInput, getTodayFrontend } from '../../utils/date'; +import { parseAddress } from '../../utils/addressParser'; import type { Delivery, PickupLocation, DeliveryStatus } from '../../types'; interface DeliveryFormProps { @@ -20,28 +21,49 @@ export const DeliveryForm = ({ isOpen, onClose, onSubmit, initialData, defaultDa const [formData, setFormData] = useState({ date: defaultDate || getTodayFrontend(), pickupLocation: 'warehouse' as PickupLocation, + pickupLocation2: null as PickupLocation | null, productName: '', + productName2: '', + customerName: '', address: '', + street: '', + house: '', + apartment: '', + entrance: '', + floor: '', phone: '', additionalPhone: '', hasElevator: false, + serviceInfo: '', comment: '', status: 'new' as DeliveryStatus, }); + const [showSecondPickup, setShowSecondPickup] = useState(false); + const [showAddressDetails, setShowAddressDetails] = useState(false); useEffect(() => { if (initialData) { setFormData({ date: initialData.date, pickupLocation: initialData.pickupLocation, + pickupLocation2: initialData.pickupLocation2 || null, productName: initialData.productName, + productName2: initialData.productName2 || '', + customerName: initialData.customerName, address: initialData.address, + street: initialData.street, + house: initialData.house, + apartment: initialData.apartment || '', + entrance: initialData.entrance || '', + floor: initialData.floor || '', phone: initialData.phone, additionalPhone: initialData.additionalPhone || '', hasElevator: initialData.hasElevator, + serviceInfo: initialData.serviceInfo || '', comment: initialData.comment, status: initialData.status, }); + setShowSecondPickup(!!initialData.pickupLocation2); } else if (defaultDate) { setFormData(prev => ({ ...prev, date: defaultDate })); } @@ -54,7 +76,7 @@ export const DeliveryForm = ({ isOpen, onClose, onSubmit, initialData, defaultDa const isPhoneValid = !formData.phone || validatePhone(formData.phone); const isAdditionalPhoneValid = !formData.additionalPhone || validatePhone(formData.additionalPhone); - const isFormValid = formData.productName && formData.address && formData.phone && isPhoneValid; + const isFormValid = formData.productName && formData.address && formData.phone && isPhoneValid && formData.customerName && formData.street && formData.house; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -65,14 +87,25 @@ export const DeliveryForm = ({ isOpen, onClose, onSubmit, initialData, defaultDa setFormData({ date: defaultDate || getTodayFrontend(), pickupLocation: 'warehouse', + pickupLocation2: null, productName: '', + productName2: '', + customerName: '', address: '', + street: '', + house: '', + apartment: '', + entrance: '', + floor: '', phone: '', additionalPhone: '', hasElevator: false, + serviceInfo: '', comment: '', status: 'new', }); + setShowSecondPickup(false); + setShowAddressDetails(false); } onClose(); } catch { @@ -126,16 +159,106 @@ export const DeliveryForm = ({ isOpen, onClose, onSubmit, initialData, defaultDa required /> + {/* Address with auto-parse */} +
+ + { + const newAddress = e.target.value; + const parsed = parseAddress(newAddress); + setFormData({ + ...formData, + address: newAddress, + street: parsed.street || formData.street, + house: parsed.house || formData.house, + apartment: parsed.apartment || formData.apartment, + entrance: parsed.entrance || formData.entrance, + floor: parsed.floor || formData.floor, + }); + if (parsed.street || parsed.house) { + setShowAddressDetails(true); + } + }} + onBlur={() => setShowAddressDetails(true)} + placeholder="ул. Абая, д. 15, кв. 45, подъезд 3, этаж 5" + className="w-full px-3 py-2 bg-[#f5f3f5] border border-[#c5c6cd] rounded-md text-[#1b1b1d] focus:outline-none focus:ring-2 focus:ring-[#1B263B] focus:border-transparent transition-colors" + required + /> +

+ Улица, дом, квартира, подъезд, этаж +

+
+ + {/* Parsed address details */} + {showAddressDetails && ( +
+

Проверьте распознанные данные:

+
+
+ + setFormData({ ...formData, street: e.target.value })} + className="w-full px-2 py-1.5 bg-white border border-[#c5c6cd] rounded text-sm" + required + /> +
+
+ + setFormData({ ...formData, house: e.target.value })} + className="w-full px-2 py-1.5 bg-white border border-[#c5c6cd] rounded text-sm" + required + /> +
+
+ + setFormData({ ...formData, apartment: e.target.value })} + className="w-full px-2 py-1.5 bg-white border border-[#c5c6cd] rounded text-sm" + /> +
+
+ + setFormData({ ...formData, entrance: e.target.value })} + className="w-full px-2 py-1.5 bg-white border border-[#c5c6cd] rounded text-sm" + /> +
+
+ + setFormData({ ...formData, floor: e.target.value })} + className="w-full px-2 py-1.5 bg-white border border-[#c5c6cd] rounded text-sm" + /> +
+
+
+ )} + setFormData({ ...formData, address: e.target.value })} - placeholder="ул. Примерная, д. 1" + label="ФИО клиента *" + value={formData.customerName} + onChange={(e) => setFormData({ ...formData, customerName: e.target.value })} + placeholder="Иванов Иван Иванович" required /> setFormData({ ...formData, phone: e.target.value })} @@ -175,6 +298,43 @@ export const DeliveryForm = ({ isOpen, onClose, onSubmit, initialData, defaultDa

)} + {/* Second pickup location */} +
+ { + setShowSecondPickup(e.target.checked); + if (!e.target.checked) { + setFormData({ ...formData, pickupLocation2: null, productName2: '' }); + } + }} + className="w-4 h-4 text-[#1B263B] border-[#c5c6cd] rounded focus:ring-[#1B263B]" + /> + +
+ + {showSecondPickup && ( +
+

Вторая точка загрузки

+ setFormData({ ...formData, productName2: e.target.value })} + placeholder="Название товара со второй точки" + /> +
+ )} +
+ setFormData({ ...formData, serviceInfo: e.target.value })} + placeholder="Сборка 5000 тг, подъём на этаж 3000 тг" + /> + Дата Загрузка Товар + Клиент Адрес Телефон Комментарий diff --git a/frontend/src/components/delivery/DeliveryRow.tsx b/frontend/src/components/delivery/DeliveryRow.tsx index 3d38ceb..475806c 100644 --- a/frontend/src/components/delivery/DeliveryRow.tsx +++ b/frontend/src/components/delivery/DeliveryRow.tsx @@ -4,6 +4,8 @@ import type { Delivery } from '../../types'; import { pickupLocationLabels } from '../../types'; import { StatusBadge } from './StatusBadge'; +const CITY = 'kokshetau'; + interface DeliveryRowProps { delivery: Delivery; onStatusChange: (id: string) => void; @@ -15,7 +17,7 @@ export const DeliveryRow = memo(({ delivery, onStatusChange, onEdit, onDelete }: const handleAddressClick = (e: React.MouseEvent) => { e.stopPropagation(); const encodedAddress = encodeURIComponent(delivery.address); - window.open(`https://maps.google.com/?q=${encodedAddress}`, '_blank'); + window.open(`https://2gis.kz/${CITY}/search/${encodedAddress}`, '_blank'); }; const handlePhoneClick = (e: React.MouseEvent) => { @@ -33,15 +35,25 @@ export const DeliveryRow = memo(({ delivery, onStatusChange, onEdit, onDelete }: /> {delivery.date} - {pickupLocationLabels[delivery.pickupLocation]} - {delivery.productName} + + {delivery.pickupLocation2 + ? `${pickupLocationLabels[delivery.pickupLocation]} + ${pickupLocationLabels[delivery.pickupLocation2]}` + : pickupLocationLabels[delivery.pickupLocation]} + + + {delivery.productName} + {delivery.productName2 && + {delivery.productName2}} + + {delivery.customerName} diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 1ed9002..bbf7e41 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -46,7 +46,7 @@ const Dashboard = ({ onDateSelect, onAddDelivery }: DashboardProps) => { }; const printDeliveries = (date: Date, dayDeliveries: Delivery[]) => { - + const printWindow = window.open('', '_blank'); if (!printWindow) return; @@ -58,31 +58,35 @@ const Dashboard = ({ onDateSelect, onAddDelivery }: DashboardProps) => {

Доставки на ${format(date, 'dd MMMM yyyy', { locale: ru })}

- + + ${dayDeliveries.map((d: Delivery) => ` - - - - + + + + + `).join('')} @@ -90,7 +94,7 @@ const Dashboard = ({ onDateSelect, onAddDelivery }: DashboardProps) => { `; - + printWindow.document.write(html); printWindow.document.close(); printWindow?.print(); diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index dbe2453..083fcda 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -5,10 +5,19 @@ export interface Delivery { id: string; date: string; // DD-MM-YYYY pickupLocation: PickupLocation; + pickupLocation2?: PickupLocation | null; productName: string; - address: string; + productName2?: string | null; + customerName: string; phone: string; additionalPhone?: string; + address: string; // full address for compatibility + street: string; + house: string; + apartment?: string; + entrance?: string; + floor?: string; + serviceInfo?: string; hasElevator: boolean; comment: string; status: DeliveryStatus; @@ -29,7 +38,6 @@ export const statusLabels: Record = { delivered: 'Доставлено', }; -// Auth types export interface LoginRequest { username: string; password: string; diff --git a/frontend/src/utils/addressParser.ts b/frontend/src/utils/addressParser.ts new file mode 100644 index 0000000..c2c75a7 --- /dev/null +++ b/frontend/src/utils/addressParser.ts @@ -0,0 +1,122 @@ +export interface ParsedAddress { + street: string; + house: string; + apartment: string; + entrance: string; + floor: string; + remaining: string; // unrecognized parts +} + +// Common Russian/Kazakh address patterns +const STREET_PREFIXES = ['ул\\.', 'ул', 'пр\\.', 'пр', 'пр-т', 'бульвар', 'пер\\.', 'пер', 'ш\\.', 'шоссе', 'тракт']; +const HOUSE_PATTERNS = ['д\\.', 'дом', 'д(?=\\s*\\d)', 'строение', 'стр\\.']; +const APARTMENT_PATTERNS = ['кв\\.', 'квартира', 'кв(?=\\s*\\d)', 'офис', 'оф\\.']; +const ENTRANCE_PATTERNS = ['подъезд', 'под\\.', 'под(?=\\s*\\d)', 'п(?=\\s*\\d)']; +const FLOOR_PATTERNS = ['этаж', 'эт\\.', 'эт(?=\\s*\\d)', 'э(?=\\s*\\d)']; + +function createPattern(prefixes: string[]): RegExp { + const prefixPart = prefixes.join('|'); + // Match prefix followed by optional spaces/separators and then the value + // Use Unicode property \p{L} for letters to support Cyrillic + return new RegExp(`(?:${prefixPart})[\\s\\.]*([0-9]+[\\p{L}\\-]*|[\\p{L}][\\p{L}\\d\\-]*)`, 'iu'); +} + +function extractValue(text: string, patterns: string[]): { value: string; remaining: string } { + const regex = createPattern(patterns); + const match = text.match(regex); + if (match) { + // Remove the matched part from text + const remaining = text.replace(match[0], '').trim().replace(/^[,.\s]+/, ''); + return { value: match[1].trim(), remaining }; + } + return { value: '', remaining: text }; +} + +export function parseAddress(address: string): ParsedAddress { + let remaining = address.trim(); + + // Extract components in order + const streetResult = extractValue(remaining, STREET_PREFIXES); + const street = streetResult.value; + remaining = streetResult.remaining; + + // Try to extract house: first standalone number at the start, then with prefix + let house = ''; + let houseResult; + + // Try standalone number first (e.g., "ул. Абая 5" - house is "5" without "д." prefix) + const standaloneHouseMatch = remaining.match(/^\s*(\d+[\p{L}]?)(?:\s*[,;]|\s+(?=кв|под|э|п\s|э\s|д\.|д\s|дом))/iu); + if (standaloneHouseMatch) { + house = standaloneHouseMatch[1]; + remaining = remaining.slice(standaloneHouseMatch[0].length).trim().replace(/^[,;\s]+/, ''); + } else { + // Fallback: try with prefix patterns + houseResult = extractValue(remaining, HOUSE_PATTERNS); + house = houseResult.value; + remaining = houseResult.remaining; + } + + const apartmentResult = extractValue(remaining, APARTMENT_PATTERNS); + const apartment = apartmentResult.value; + remaining = apartmentResult.remaining; + + const entranceResult = extractValue(remaining, ENTRANCE_PATTERNS); + const entrance = entranceResult.value; + remaining = entranceResult.remaining; + + const floorResult = extractValue(remaining, FLOOR_PATTERNS); + const floor = floorResult.value; + remaining = floorResult.remaining; + + // Clean up remaining - remove common separators + remaining = remaining + .replace(/^[,.\s]+/, '') + .replace(/[,.\s]+$/, '') + .trim(); + + return { + street, + house, + apartment, + entrance, + floor, + remaining + }; +} + +// Format address for display +export function formatAddressShort(addr: ParsedAddress): string { + const parts: string[] = []; + if (addr.street) parts.push(addr.street); + if (addr.house) parts.push(`д. ${addr.house}`); + if (addr.apartment) parts.push(`кв. ${addr.apartment}`); + return parts.join(', ') || addr.remaining; +} + +export function formatAddressDetails(addr: ParsedAddress): string { + const parts: string[] = []; + if (addr.entrance) parts.push(`Подъезд ${addr.entrance}`); + if (addr.floor) parts.push(`этаж ${addr.floor}`); + return parts.join(', '); +} + +// Build full address from components +export function buildFullAddress( + street: string, + house: string, + apartment?: string, + entrance?: string, + floor?: string +): string { + const parts: string[] = []; + if (street) parts.push(street); + if (house) parts.push(`д. ${house}`); + if (apartment) parts.push(`кв. ${apartment}`); + if (entrance || floor) { + const details: string[] = []; + if (entrance) details.push(`подъезд ${entrance}`); + if (floor) details.push(`этаж ${floor}`); + parts.push(details.join(', ')); + } + return parts.join(', '); +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index a205fe8..0bbc1d4 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -29,7 +29,7 @@ export default defineConfig({ allowedHosts: ['delivery.loca.lt', '.loca.lt'], proxy: { '/api': { - target: 'http://localhost:8080', + target: 'http://192.168.3.96:8080', changeOrigin: true, }, }, diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 43772a5..932e243 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -797,6 +797,28 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.28.5" +"@emnapi/core@^1.8.1": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.10.0.tgz#380ccc8f2412ea22d1d972df7f8ee23a3b9c7467" + integrity sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw== + dependencies: + "@emnapi/wasi-threads" "1.2.1" + tslib "^2.4.0" + +"@emnapi/runtime@^1.8.1": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.10.0.tgz#4b260c0d3534204e98c6110b8db1a987d26ec87c" + integrity sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA== + dependencies: + tslib "^2.4.0" + +"@emnapi/wasi-threads@1.2.1", "@emnapi/wasi-threads@^1.1.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz#28fed21a1ba1ce797c44a070abc94d42f3ae8548" + integrity sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w== + dependencies: + tslib "^2.4.0" + "@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1": version "4.9.1" resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz" @@ -847,7 +869,7 @@ minimatch "^3.1.5" strip-json-comments "^3.1.1" -"@eslint/js@^9.39.4", "@eslint/js@9.39.4": +"@eslint/js@9.39.4", "@eslint/js@^9.39.4": version "9.39.4" resolved "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz" integrity sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw== @@ -935,16 +957,95 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@napi-rs/wasm-runtime@^1.1.1": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz#a46bbfedc29751b7170c5d23bc1d8ee8c7e3c1e1" + integrity sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow== + dependencies: + "@tybys/wasm-util" "^0.10.1" + "@oxc-project/types@=0.122.0": version "0.122.0" resolved "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz" integrity sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA== +"@rolldown/binding-android-arm64@1.0.0-rc.12": + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz#4e6af08b89da02596cc5da4b105082b68673ffec" + integrity sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA== + "@rolldown/binding-darwin-arm64@1.0.0-rc.12": version "1.0.0-rc.12" resolved "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz" integrity sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg== +"@rolldown/binding-darwin-x64@1.0.0-rc.12": + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz#eddf6aa3ed3509171fe21711f1e8ec8e0fd7ec49" + integrity sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw== + +"@rolldown/binding-freebsd-x64@1.0.0-rc.12": + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz#2102dfed19fd1f1b53435fcaaf0bc61129a266a3" + integrity sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q== + +"@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12": + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz#b2c13f40e990fd1e1935492850536c768c961a0f" + integrity sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q== + +"@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12": + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz#32ca9f77c1e76b2913b3d53d2029dc171c0532d6" + integrity sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg== + +"@rolldown/binding-linux-arm64-musl@1.0.0-rc.12": + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz#f4337ddd52f0ed3ada2105b59ee1b757a2c4858c" + integrity sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw== + +"@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12": + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz#22fdd14cb00ee8208c28a39bab7f28860ec6705d" + integrity sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g== + +"@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12": + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz#838215096d1de6d3d509e0410801cb7cda8161ff" + integrity sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og== + +"@rolldown/binding-linux-x64-gnu@1.0.0-rc.12": + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz#f7d71d97f6bd43198596b26dc2cb364586e12673" + integrity sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg== + +"@rolldown/binding-linux-x64-musl@1.0.0-rc.12": + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz#a2ca737f01b0ad620c4c404ca176ea3e3ad804c3" + integrity sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig== + +"@rolldown/binding-openharmony-arm64@1.0.0-rc.12": + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz#f66317e29eafcc300bed7af8dddac26ab3b1bf82" + integrity sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA== + +"@rolldown/binding-wasm32-wasi@1.0.0-rc.12": + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz#8825523fdffa1f1dc4683be9650ffaa9e4a77f04" + integrity sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg== + dependencies: + "@napi-rs/wasm-runtime" "^1.1.1" + +"@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12": + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz#4f3a17e3d68a58309c27c0930b0f7986ccabef47" + integrity sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q== + +"@rolldown/binding-win32-x64-msvc@1.0.0-rc.12": + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz#d762765d5660598a96b570b513f535c151272985" + integrity sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw== + "@rolldown/pluginutils@1.0.0-rc.12": version "1.0.0-rc.12" resolved "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz" @@ -1032,11 +1133,73 @@ source-map-js "^1.2.1" tailwindcss "4.2.2" +"@tailwindcss/oxide-android-arm64@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz#61d9ec5c18394fe7a972e99e19e6065e833da77c" + integrity sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg== + "@tailwindcss/oxide-darwin-arm64@4.2.2": version "4.2.2" resolved "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz" integrity sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg== +"@tailwindcss/oxide-darwin-x64@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz#a5899f1fbe55c4eddcbc871b835d5183ba34658c" + integrity sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw== + +"@tailwindcss/oxide-freebsd-x64@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz#76185bb1bea9af915a5b9f465323861646587e21" + integrity sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ== + +"@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz#74c17c69b2015f7600d566ab0990aaac8701128e" + integrity sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ== + +"@tailwindcss/oxide-linux-arm64-gnu@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz#38a846d9d5795bc3b57951172044d8dbb3c79aa6" + integrity sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw== + +"@tailwindcss/oxide-linux-arm64-musl@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz#f4cc4129c17d3f2bcb01efef4d7a2f381e5e3f53" + integrity sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag== + +"@tailwindcss/oxide-linux-x64-gnu@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz#7c4a00b0829e12736bd72ec74e1c08205448cc2e" + integrity sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg== + +"@tailwindcss/oxide-linux-x64-musl@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz#711756d7bbe97e221fc041b63a4f385b85ba4321" + integrity sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ== + +"@tailwindcss/oxide-wasm32-wasi@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz#ed6d28567b7abb8505f824457c236d2cd07ee18e" + integrity sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q== + dependencies: + "@emnapi/core" "^1.8.1" + "@emnapi/runtime" "^1.8.1" + "@emnapi/wasi-threads" "^1.1.0" + "@napi-rs/wasm-runtime" "^1.1.1" + "@tybys/wasm-util" "^0.10.1" + tslib "^2.8.1" + +"@tailwindcss/oxide-win32-arm64-msvc@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz#f2d0360e5bc06fe201537fb08193d3780e7dd24f" + integrity sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ== + +"@tailwindcss/oxide-win32-x64-msvc@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz#10fc71b73883f9c3999b5b8c338fd96a45240dcb" + integrity sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA== + "@tailwindcss/oxide@4.2.2": version "4.2.2" resolved "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz" @@ -1064,16 +1227,23 @@ "@tailwindcss/oxide" "4.2.2" tailwindcss "4.2.2" -"@types/estree@^1.0.0", "@types/estree@^1.0.6": - version "1.0.8" - resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz" - integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== +"@tybys/wasm-util@^0.10.1": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414" + integrity sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg== + dependencies: + tslib "^2.4.0" "@types/estree@0.0.39": version "0.0.39" resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/estree@^1.0.0", "@types/estree@^1.0.6": + version "1.0.8" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + "@types/json-schema@^7.0.15": version "7.0.15" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" @@ -1155,7 +1325,7 @@ "@typescript-eslint/types" "8.57.2" "@typescript-eslint/visitor-keys" "8.57.2" -"@typescript-eslint/tsconfig-utils@^8.57.2", "@typescript-eslint/tsconfig-utils@8.57.2": +"@typescript-eslint/tsconfig-utils@8.57.2", "@typescript-eslint/tsconfig-utils@^8.57.2": version "8.57.2" resolved "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz" integrity sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw== @@ -1171,7 +1341,7 @@ debug "^4.4.3" ts-api-utils "^2.4.0" -"@typescript-eslint/types@^8.57.2", "@typescript-eslint/types@8.57.2": +"@typescript-eslint/types@8.57.2", "@typescript-eslint/types@^8.57.2": version "8.57.2" resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz" integrity sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA== @@ -1355,14 +1525,7 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -brace-expansion@^5.0.2: - version "5.0.5" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz" - integrity sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ== - dependencies: - balanced-match "^4.0.2" - -brace-expansion@^5.0.5: +brace-expansion@^5.0.2, brace-expansion@^5.0.5: version "5.0.5" resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz" integrity sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ== @@ -2420,12 +2583,62 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lightningcss-android-arm64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz#f033885116dfefd9c6f54787523e3514b61e1968" + integrity sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg== + lightningcss-darwin-arm64@1.32.0: version "1.32.0" resolved "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz" integrity sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ== -lightningcss@^1.32.0, lightningcss@1.32.0: +lightningcss-darwin-x64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz#35f3e97332d130b9ca181e11b568ded6aebc6d5e" + integrity sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w== + +lightningcss-freebsd-x64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz#9777a76472b64ed6ff94342ad64c7bafd794a575" + integrity sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig== + +lightningcss-linux-arm-gnueabihf@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz#13ae652e1ab73b9135d7b7da172f666c410ad53d" + integrity sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw== + +lightningcss-linux-arm64-gnu@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz#417858795a94592f680123a1b1f9da8a0e1ef335" + integrity sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ== + +lightningcss-linux-arm64-musl@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz#6be36692e810b718040802fd809623cffe732133" + integrity sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg== + +lightningcss-linux-x64-gnu@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz#0b7803af4eb21cfd38dd39fe2abbb53c7dd091f6" + integrity sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA== + +lightningcss-linux-x64-musl@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz#88dc8ba865ddddb1ac5ef04b0f161804418c163b" + integrity sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg== + +lightningcss-win32-arm64-msvc@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz#4f30ba3fa5e925f5b79f945e8cc0d176c3b1ab38" + integrity sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw== + +lightningcss-win32-x64-msvc@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz#141aa5605645064928902bb4af045fa7d9f4220a" + integrity sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q== + +lightningcss@1.32.0, lightningcss@^1.32.0: version "1.32.0" resolved "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz" integrity sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ== @@ -2488,14 +2701,7 @@ lucide-react@^1.7.0: resolved "https://registry.npmjs.org/lucide-react/-/lucide-react-1.7.0.tgz" integrity sha512-yI7BeItCLZJTXikmK4KNUGCKoGzSvbKlfCvw44bU4fXAL6v3gYS4uHD1jzsLkfwODYwI6Drw5Tu9Z5ulDe0TSg== -magic-string@^0.25.0: - version "0.25.9" - resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz" - integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== - dependencies: - sourcemap-codec "^1.4.8" - -magic-string@^0.25.7: +magic-string@^0.25.0, magic-string@^0.25.7: version "0.25.9" resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz" integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== @@ -3108,7 +3314,7 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -tailwindcss@^4.2.2, tailwindcss@4.2.2: +tailwindcss@4.2.2, tailwindcss@^4.2.2: version "4.2.2" resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz" integrity sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q== @@ -3163,6 +3369,11 @@ ts-api-utils@^2.4.0: resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz" integrity sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA== +tslib@^2.4.0, tslib@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" @@ -3558,7 +3769,7 @@ workbox-sw@7.4.0: resolved "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.4.0.tgz" integrity sha512-ltU+Kr3qWR6BtbdlMnCjobZKzeV1hN+S6UvDywBrwM19TTyqA03X66dzw1tEIdJvQ4lYKkBFox6IAEhoSEZ8Xw== -workbox-window@^7.4.0, workbox-window@7.4.0: +workbox-window@7.4.0, workbox-window@^7.4.0: version "7.4.0" resolved "https://registry.npmjs.org/workbox-window/-/workbox-window-7.4.0.tgz" integrity sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw==
Статус Загрузка ТоварКлиент Адрес ТелефонУслуги Комментарий
${d.status === 'new' ? 'Новое' : 'Доставлено'}${pickupLocationLabels[d.pickupLocation]}${d.productName}${d.address}${d.pickupLocation2 ? pickupLocationLabels[d.pickupLocation] + ' + ' + pickupLocationLabels[d.pickupLocation2] : pickupLocationLabels[d.pickupLocation]}${d.productName}${d.productName2 ? '
+ ' + d.productName2 + '' : ''}
${d.customerName} + ул. ${d.street}, д. ${d.house}${d.apartment ? ', кв. ' + d.apartment : ''} + ${d.entrance || d.floor ? '
' + (d.entrance ? 'Подъезд ' + d.entrance : '') + (d.entrance && d.floor ? ', ' : '') + (d.floor ? 'этаж ' + d.floor : '') + '' : ''} +
${d.phone}${d.serviceInfo || '-'} ${d.comment || '-'}