Переглянути джерело

fix: replace broken PlaceEditModal modal with navigation to /places/edit/[id]

neyrogovnarik 1 місяць тому
батько
коміт
20b205e234
1 змінених файлів з 5 додано та 244 видалено
  1. 5 244
      frontend/src/app/places/my/page.tsx

+ 5 - 244
frontend/src/app/places/my/page.tsx

@@ -1,11 +1,9 @@
 'use client'
 
 import { useEffect, useState, useCallback } from 'react'
-import dynamic from 'next/dynamic'
-import { api, ApiRequestError } from '@/lib/api'
-import type { Place, Tag, Feature } from '@/types'
-
-const MapPicker = dynamic(() => import('@/components/MapPicker'), { ssr: false })
+import { useRouter } from 'next/navigation'
+import { api } from '@/lib/api'
+import type { Place } from '@/types'
 
 const STATUS_LABELS: Record<string, { label: string; color: string }> = {
   published: { label: 'Опубликовано', color: 'text-green-400' },
@@ -15,239 +13,6 @@ const STATUS_LABELS: Record<string, { label: string; color: string }> = {
   draft: { label: 'Черновик', color: 'text-white/40' },
 }
 
-function PlaceEditModal({ placeId, onClose, onSaved }: { placeId: string; onClose: () => void; onSaved: () => void }) {
-  const [place, setPlace] = useState<Place | null>(null)
-  const [loadingPlace, setLoadingPlace] = useState(true)
-  const [tags, setTags] = useState<Tag[]>([])
-  const [features, setFeatures] = useState<Feature[]>([])
-  const [saving, setSaving] = useState(false)
-  const [error, setError] = useState('')
-
-  const [title, setTitle] = useState('')
-  const [description, setDescription] = useState('')
-  const [address, setAddress] = useState('')
-  const [accessInfo, setAccessInfo] = useState('')
-  const [selectedTags, setSelectedTags] = useState<string[]>([])
-  const [selectedFeatures, setSelectedFeatures] = useState<string[]>([])
-  const [lat, setLat] = useState('')
-  const [lng, setLng] = useState('')
-  const [coverFile, setCoverFile] = useState<File | null>(null)
-  const [coverPreview, setCoverPreview] = useState('')
-  const [hourlyRate, setHourlyRate] = useState('')
-  const [minHours, setMinHours] = useState('1')
-  const [showMapPicker, setShowMapPicker] = useState(false)
-
-  useEffect(() => {
-    api.get<Place>(`/places/${placeId}`)
-      .then((p) => {
-        setPlace(p)
-        setTitle(p.title || '')
-        setDescription(p.description || '')
-        setAddress(p.address || '')
-        setAccessInfo(p.access_info || '')
-        setSelectedTags(p.tags?.map((t) => t.id) || [])
-        setSelectedFeatures(p.features?.map((f) => f.id) || [])
-        setLat(p.lat?.toString() || '')
-        setLng(p.lng?.toString() || '')
-        setCoverPreview(p.cover_image || '')
-        setHourlyRate(p.hourly_rate?.toString() || '')
-        setMinHours(p.min_hours?.toString() || '1')
-      })
-      .catch(() => onClose())
-      .finally(() => setLoadingPlace(false))
-
-    api.get<Tag[]>('/tags').then(setTags).catch(() => setError('Ошибка загрузки тегов'))
-    api.get<Feature[]>('/features').then(setFeatures).catch(() => setError('Ошибка загрузки характеристик'))
-  }, [placeId, onClose])
-
-  const handleMapPick = (pickLat: number, pickLng: number, pickAddress?: string) => {
-    setLat(pickLat.toFixed(6))
-    setLng(pickLng.toFixed(6))
-    if (pickAddress) setAddress(pickAddress)
-    setShowMapPicker(false)
-  }
-
-  const handleSubmit = async (e: React.FormEvent) => {
-    e.preventDefault()
-    setSaving(true)
-    setError('')
-
-    try {
-      let coverImage: string | undefined
-      if (coverFile) {
-        const formData = new FormData()
-        formData.append('file', coverFile)
-        const res = await api.upload<{ file_url: string }>('/upload', formData)
-        coverImage = res.file_url
-      }
-
-      const body: Record<string, unknown> = {
-        title,
-        description: description || undefined,
-        address: address || undefined,
-        lat: parseFloat(lat),
-        lng: parseFloat(lng),
-        access_info: accessInfo || undefined,
-        tags: selectedTags,
-        features: selectedFeatures,
-        cover_image: coverImage,
-      }
-
-      if (place?.type === 'studio') {
-        body.hourly_rate = hourlyRate ? parseInt(hourlyRate) : undefined
-        body.min_hours = parseInt(minHours) || 1
-        body.currency = 'RUB'
-      }
-
-      await api.patch(`/places/${placeId}`, body)
-      onSaved()
-    } catch (err: any) {
-      setError(err instanceof ApiRequestError ? err.message : 'Ошибка при сохранении')
-    } finally {
-      setSaving(false)
-    }
-  }
-
-  const toggleTag = (id: string) => {
-    setSelectedTags((prev) =>
-      prev.includes(id) ? prev.filter((t) => t !== id) : [...prev, id]
-    )
-  }
-
-  const toggleFeature = (id: string) => {
-    setSelectedFeatures((prev) =>
-      prev.includes(id) ? prev.filter((f) => f !== id) : [...prev, id]
-    )
-  }
-
-  return (
-    <>
-      <div className="fixed inset-0 z-50 flex items-start justify-center overflow-y-auto bg-black/60 pt-10 pb-10" onClick={onClose}>
-        <div className="w-full max-w-2xl rounded-xl bg-[#1e293b] shadow-xl" onClick={(e) => e.stopPropagation()}>
-          {loadingPlace ? (
-            <div className="flex items-center justify-center p-8">
-              <p className="text-white/40">Загрузка...</p>
-            </div>
-          ) : (
-            <form onSubmit={handleSubmit} className="space-y-5 p-6">
-              <h2 className="text-2xl font-bold text-white">Редактировать</h2>
-
-              {error && <p className="rounded-lg bg-red-500/10 p-3 text-sm text-red-400">{error}</p>}
-
-              <div>
-                <label className="mb-1 block text-sm text-white/60">Название *</label>
-                <input value={title} onChange={(e) => setTitle(e.target.value)}
-                  className="w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white outline-none focus:border-green-500" required />
-              </div>
-
-              <div>
-                <label className="mb-1 block text-sm text-white/60">Описание</label>
-                <textarea value={description} onChange={(e) => setDescription(e.target.value)}
-                  rows={3} className="w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white outline-none focus:border-green-500" />
-              </div>
-
-              <div>
-                <label className="mb-1 block text-sm text-white/60">Адрес</label>
-                <input value={address} onChange={(e) => setAddress(e.target.value)}
-                  className="w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white outline-none focus:border-green-500" />
-              </div>
-
-              <div className="grid grid-cols-5 gap-4">
-                <div className="col-span-2">
-                  <label className="mb-1 block text-sm text-white/60">Широта</label>
-                  <input type="number" step="any" value={lat} onChange={(e) => setLat(e.target.value)}
-                    className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-3 text-white outline-none focus:border-green-500" />
-                </div>
-                <div className="col-span-2">
-                  <label className="mb-1 block text-sm text-white/60">Долгота</label>
-                  <input type="number" step="any" value={lng} onChange={(e) => setLng(e.target.value)}
-                    className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-3 text-white outline-none focus:border-green-500" />
-                </div>
-                <div className="col-span-1 flex items-end">
-                  <button type="button" onClick={() => setShowMapPicker(true)}
-                    className="h-[50px] w-auto rounded-lg border border-white/10 bg-white/5 px-3 py-1.5 text-sm text-white/80 hover:bg-white/10 transition text-center leading-tight"
-                  >Указать<br />на карте</button>
-                </div>
-              </div>
-
-              <div>
-                <label className="mb-1 block text-sm text-white/60">Информация о доступе</label>
-                <textarea value={accessInfo} onChange={(e) => setAccessInfo(e.target.value)}
-                  rows={2} className="w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white outline-none focus:border-green-500"
-                  placeholder="Как добраться, парковка..." />
-              </div>
-
-              <div>
-                <label className="mb-1 block text-sm text-white/60">Обложка</label>
-                <input type="file" accept="image/*" onChange={(e) => {
-                  const file = e.target.files?.[0]
-                  if (file) {
-                    setCoverFile(file)
-                    if (coverPreview) URL.revokeObjectURL(coverPreview)
-                    setCoverPreview(URL.createObjectURL(file))
-                  }
-                }}
-                  className="w-full text-sm text-white/60 file:mr-4 file:rounded-lg file:border-0 file:bg-green-600 file:px-4 file:py-2 file:text-white file:hover:bg-green-500" />
-                {coverPreview && (
-                  <img src={coverPreview} alt="preview" className="mt-2 h-32 rounded-lg object-cover" />
-                )}
-              </div>
-
-              {place?.type === 'studio' && (
-                <div className="grid grid-cols-2 gap-4">
-                  <div>
-                    <label className="mb-1 block text-sm text-white/60">Цена за час (₽)</label>
-                    <input type="number" value={hourlyRate} onChange={(e) => setHourlyRate(e.target.value)}
-                      className="w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white outline-none focus:border-green-500" />
-                  </div>
-                  <div>
-                    <label className="mb-1 block text-sm text-white/60">Мин. часов</label>
-                    <input type="number" min="1" value={minHours} onChange={(e) => setMinHours(e.target.value)}
-                      className="w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white outline-none focus:border-green-500" />
-                  </div>
-                </div>
-              )}
-
-              <div>
-                <label className="mb-2 block text-sm text-white/60">Теги (стили съёмки)</label>
-                <div className="flex flex-wrap gap-2">
-                  {tags.map((tag) => (
-                    <button key={tag.id} type="button" onClick={() => toggleTag(tag.id)}
-                      className={`rounded-full px-3 py-1.5 text-sm transition ${selectedTags.includes(tag.id) ? 'bg-green-600 text-white' : 'bg-white/10 text-white/60 hover:bg-white/20'}`}
-                    >{tag.name}</button>
-                  ))}
-                </div>
-              </div>
-
-              <div>
-                <label className="mb-2 block text-sm text-white/60">Характеристики</label>
-                <div className="flex flex-wrap gap-2">
-                  {features.map((feat) => (
-                    <button key={feat.id} type="button" onClick={() => toggleFeature(feat.id)}
-                      className={`rounded-full px-3 py-1.5 text-sm transition ${selectedFeatures.includes(feat.id) ? 'bg-green-600 text-white' : 'bg-white/10 text-white/60 hover:bg-white/20'}`}
-                    >{feat.name}</button>
-                  ))}
-                </div>
-              </div>
-
-              <div className="flex items-center gap-3 pt-2">
-                <button type="button" onClick={onClose}
-                  className="rounded-lg bg-white/10 px-6 py-3 text-white hover:bg-white/20 transition"
-                >Отмена</button>
-                <button type="submit" disabled={saving}
-                  className="flex-1 rounded-lg bg-green-600 px-6 py-3 font-medium text-white hover:bg-green-500 transition disabled:opacity-50"
-                >{saving ? 'Сохранение...' : 'Сохранить'}</button>
-              </div>
-            </form>
-          )}
-        </div>
-      </div>
-
-      {showMapPicker && <MapPicker onSelect={handleMapPick} onClose={() => setShowMapPicker(false)} />}
-    </>
-  )
-}
-
 const FILTERS: { key: string; label: string }[] = [
   { key: 'all', label: 'Все' },
   { key: 'draft', label: 'Черновики' },
@@ -321,9 +86,9 @@ function PlaceCard({ place, onEdit, onDeleted }: { place: Place; onEdit: () => v
 }
 
 export default function MyPlacesPage() {
+  const router = useRouter()
   const [places, setPlaces] = useState<Place[]>([])
   const [loading, setLoading] = useState(true)
-  const [editId, setEditId] = useState<string | null>(null)
   const [filter, setFilter] = useState('all')
   const [toast, setToast] = useState('')
 
@@ -381,7 +146,7 @@ export default function MyPlacesPage() {
                 <PlaceCard
                   key={place.id}
                   place={place}
-                  onEdit={() => setEditId(place.id)}
+                  onEdit={() => router.push('/places/edit/' + place.id)}
                   onDeleted={() => {
                     setPlaces((prev) => prev.filter((p) => p.id !== place.id))
                     setToast('Место удалено')
@@ -393,10 +158,6 @@ export default function MyPlacesPage() {
           )
         })()}
       </div>
-
-      {editId && (
-        <PlaceEditModal placeId={editId} onClose={() => setEditId(null)} onSaved={() => { setEditId(null); setToast('Сохранено'); setTimeout(() => setToast(''), 3000); fetchPlaces() }} />
-      )}
     </div>
   )
 }