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:
2026-03-26 01:34:31 +03:00
parent bc0f23df34
commit 4b6443c867
5 changed files with 179 additions and 69 deletions
+15 -3
View File
@@ -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;
+8 -1
View File
@@ -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>