feat: news improvements — crop preview, auto-date, validation, add-to-top
- Draggable focal point crop preview for news images (admin + user + modal) - Auto-set date+time on creation, remove date picker - Draft validation: title, text, image required — "Черновик" badge if missing - Empty/draft news filtered from user side - ArrayEditor: addPosition="top" option, fix new item expand + index shift - News sorted newest first, "Показать ещё" pagination
This commit is contained in:
@@ -14,11 +14,18 @@ interface NewsProps {
|
||||
|
||||
function formatDate(iso: string): string {
|
||||
try {
|
||||
return new Date(iso).toLocaleDateString("ru-RU", {
|
||||
const d = new Date(iso);
|
||||
const date = d.toLocaleDateString("ru-RU", {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
});
|
||||
// Show time only if it's a full ISO timestamp (not just date)
|
||||
if (iso.includes("T")) {
|
||||
const time = d.toLocaleTimeString("ru-RU", { hour: "2-digit", minute: "2-digit" });
|
||||
return `${date}, ${time}`;
|
||||
}
|
||||
return date;
|
||||
} catch {
|
||||
return iso;
|
||||
}
|
||||
@@ -45,6 +52,7 @@ function FeaturedArticle({
|
||||
loading="lazy"
|
||||
sizes="(min-width: 768px) 80vw, 100vw"
|
||||
className="object-cover transition-transform duration-700 group-hover:scale-105"
|
||||
style={{ objectPosition: `${item.imageFocalX ?? 50}% ${item.imageFocalY ?? 50}%` }}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/30 to-transparent" />
|
||||
</div>
|
||||
@@ -88,6 +96,7 @@ function CompactArticle({
|
||||
loading="lazy"
|
||||
sizes="112px"
|
||||
className="object-cover transition-transform duration-500 group-hover:scale-105"
|
||||
style={{ objectPosition: `${item.imageFocalX ?? 50}% ${item.imageFocalY ?? 50}%` }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -114,8 +123,11 @@ export function News({ data }: NewsProps) {
|
||||
|
||||
if (!data.items || data.items.length === 0) return null;
|
||||
|
||||
// Sort by date, newest first
|
||||
const sorted = [...data.items].sort((a, b) => (b.date || "").localeCompare(a.date || ""));
|
||||
// Filter out empty/draft items, sort by date newest first
|
||||
const sorted = [...data.items]
|
||||
.filter((item) => item.title.trim() && item.text.trim() && item.image)
|
||||
.sort((a, b) => (b.date || "").localeCompare(a.date || ""));
|
||||
if (sorted.length === 0) return null;
|
||||
const [featured, ...rest] = sorted;
|
||||
const visibleRest = showAll ? rest : rest.slice(0, INITIAL_VISIBLE - 1);
|
||||
const hasMore = rest.length > INITIAL_VISIBLE - 1 && !showAll;
|
||||
|
||||
@@ -13,11 +13,17 @@ interface NewsModalProps {
|
||||
|
||||
function formatDate(iso: string): string {
|
||||
try {
|
||||
return new Date(iso).toLocaleDateString("ru-RU", {
|
||||
const d = new Date(iso);
|
||||
const date = d.toLocaleDateString("ru-RU", {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
});
|
||||
if (iso.includes("T")) {
|
||||
const time = d.toLocaleTimeString("ru-RU", { hour: "2-digit", minute: "2-digit" });
|
||||
return `${date}, ${time}`;
|
||||
}
|
||||
return date;
|
||||
} catch {
|
||||
return iso;
|
||||
}
|
||||
@@ -76,6 +82,7 @@ export function NewsModal({ item, onClose }: NewsModalProps) {
|
||||
fill
|
||||
sizes="(min-width: 768px) 672px, 100vw"
|
||||
className="object-cover"
|
||||
style={{ objectPosition: `${item.imageFocalX ?? 50}% ${item.imageFocalY ?? 50}%` }}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-[#0a0a0a] via-transparent to-transparent" />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user