feat(pos): add editable showcase creation date for kits

- Add showcase_created_at field to ProductKit model
- Display days ago as badge in product card (0 дней, 1 день, etc.)
- Add date input field in edit modal
- Auto-set current date/time for new showcase kits

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-24 01:37:27 +03:00
parent fce8d9eb6e
commit 977ee91fee
6 changed files with 184 additions and 42 deletions

View File

@@ -158,49 +158,41 @@ class ImageProcessor:
@staticmethod
def _resize_image(img, size):
"""
Изменяет размер изображения с сохранением пропорций.
Изменяет размер изображения с center-crop до точного квадратного размера.
НЕ увеличивает маленькие изображения (сохраняет качество).
Создает адаптивный квадрат по размеру реального изображения.
Создает квадратное изображение без белых полей.
Args:
img: PIL Image object
size: Кортеж (width, height) - максимальный целевой размер
size: Кортеж (width, height) - целевой размер (обычно квадратный)
Returns:
PIL Image object - квадратное изображение с минимальным белым фоном
PIL Image object - квадратное изображение без белых полей
"""
# Копируем изображение, чтобы не модифицировать оригинал
img_copy = img.copy()
target_width, target_height = size
# Вычисляем пропорции исходного изображения и целевого размера
img_aspect = img_copy.width / img_copy.height
target_aspect = size[0] / size[1]
# Шаг 1: Center crop для получения квадрата
# Определяем минимальную сторону (будет размер квадрата)
min_side = min(img_copy.width, img_copy.height)
# Определяем, какой размер будет ограничивающим при масштабировании
if img_aspect > target_aspect:
# Изображение шире - ограничиваемый размер это ширина
new_width = min(img_copy.width, size[0])
new_height = int(new_width / img_aspect)
# Вычисляем координаты для обрезки из центра
left = (img_copy.width - min_side) // 2
top = (img_copy.height - min_side) // 2
right = left + min_side
bottom = top + min_side
# Обрезаем до квадрата
img_cropped = img_copy.crop((left, top, right, bottom))
# Шаг 2: Масштабируем до целевого размера (если исходный квадрат больше цели)
# Не увеличиваем маленькие изображения
if min_side > target_width:
img_resized = img_cropped.resize((target_width, target_height), Image.Resampling.LANCZOS)
else:
# Изображение выше - ограничиваемый размер это высота
new_height = min(img_copy.height, size[1])
new_width = int(new_height * img_aspect)
img_resized = img_cropped
# Масштабируем только если необходимо (не увеличиваем маленькие изображения)
if img_copy.width > new_width or img_copy.height > new_height:
img_copy = img_copy.resize((new_width, new_height), Image.Resampling.LANCZOS)
# Создаем адаптивный квадрат по размеру реального изображения (а не по конфигурации)
# Это позволяет избежать огромных белых полей для маленьких фото
square_size = max(img_copy.width, img_copy.height)
new_img = Image.new('RGB', (square_size, square_size), (255, 255, 255))
# Центрируем исходное изображение на белом фоне
offset_x = (square_size - img_copy.width) // 2
offset_y = (square_size - img_copy.height) // 2
new_img.paste(img_copy, (offset_x, offset_y))
return new_img
return img_resized
@staticmethod
def _make_square_image(img, max_size):