fix: resolve backend TS compilation errors, startup order, and add clipboard paste upload
- Fix products.service.ts: ingredients -> ingredientList (Prisma relation name) - Fix seed-blogs.ts: template literal refactor (array join) to avoid esbuild parse error - Fix seed-custom.ts: category string -> connectOrCreate relation - Add start-dev.js: sequential startup (backend first, then frontends) - Add Ctrl+V paste upload to admin MediaSelector component - Create missing images/vets/ and images/slider/ directories
This commit is contained in:
parent
0ea6c6d271
commit
cd17a532d9
@ -20,28 +20,59 @@ async function main() {
|
||||
});
|
||||
}
|
||||
|
||||
const blog1Content = [
|
||||
'<p>کلسیم یکی از مهمترین مواد معدنی برای رشد ساختار استخوانی است. تولهسگها در ماههای اول زندگی به کلسیم بیشتری نیاز دارند تا اسکلتبندی محکمی داشته باشند.</p>',
|
||||
'',
|
||||
'<h2>نسبت Ca:P (کلسیم به فسفر)</h2>',
|
||||
'<p>نسبت ایدهآل کلسیم به فسفر در رژیم تولهسگها <strong>۱.۲:۱ تا ۱.۵:۱</strong> است. عدم تعادل در این نسبت میتواند منجر به ناهنجاریهای استخوانی مانند پاهای پرانتزی و دیسپلازی هیپ شود. <strong>پودر پوسته تخم مرغ کانینو</strong> با ۳۶٪ کلسیم خالص و فسفر ۰.۱٪ بهترین گزینه برای تنظیم این نسبت است.</p>',
|
||||
'',
|
||||
'<h2>نقش ویتامین D3 در جذب کلسیم</h2>',
|
||||
'<p>ویتامین D3 (کولهکلسیفرول) جذب کلسیم از روده را تا ۳۰٪ افزایش میدهد. <strong>مصرف همزمان ویتامین D2 (ارگوکلسیفرول) با D3 مجاز نیست</strong> زیرا این دو با یکدیگر رقابت کرده و جذب مؤثر را کاهش میدهند.</p>',
|
||||
'',
|
||||
'<h2>محصولات توصیه شده برای تولهسگها</h2>',
|
||||
'<ul>',
|
||||
'<li><strong>CANHYDROX GAG:</strong> از ۸ هفتگی برای استحکام اسکلت و دندانها (۲ قرص به ازای ۱۰kg تا ۱۵ ماهگی)</li>',
|
||||
'<li><strong>Welpenmilch:</strong> شیر جایگزین مادر از روز اول (غنی از تورین و ویتامینها)</li>',
|
||||
'<li><strong>Welpenbrei:</strong> فرنی پروتئین بالا از هفته سوم</li>',
|
||||
'<li><strong>Eierschalenpulver:</strong> پودر پوسته تخم مرغ برای تنظیم نسبت Ca:P</li>',
|
||||
'</ul>',
|
||||
'',
|
||||
'<h2>هشدار برای نژادهای بزرگ</h2>',
|
||||
'<p>نژادهای بزرگ (ژرمن شپرد، لابرادور، گلدن رتریور و ...) مستعد دیسپلازی هیپ و مشکلات مفصلی هستند. مصرف مکملهای ارتوپدی مانند <strong>Flexan</strong> (کلاژن هیدرولیز شده با صدف لب سبز) از دوران تولهگی توصیه میشود.</p>'
|
||||
].join('\n');
|
||||
|
||||
const blog2Content = [
|
||||
'<p>ویتامینهای B Complex (B1، B2، B6، B12 و ...) کوآنزیمهای ضروری چرخه کربس هستند که انرژی سلولی (ATP) را تولید میکنند. کمبود این ویتامینها منجر به خستگی مزمن، ریزش مو و التهابات پوستی میشود.</p>',
|
||||
'',
|
||||
'<h2>هر ویتامین B چه میکند؟</h2>',
|
||||
'<ul>',
|
||||
'<li><strong>B1 (تیامین):</strong> متابولیسم کربوهیدراتها و عملکرد سیستم عصبی</li>',
|
||||
'<li><strong>B2 (ریبوفلاوین):</strong> تولید انرژی و سلامت پوست</li>',
|
||||
'<li><strong>B6 (پیریدوکسین):</strong> سنتز پروتئین و تولید گلبول قرمز</li>',
|
||||
'<li><strong>B7 (بیوتین / ویتامین H):</strong> کراتینسازی مو و پنجه - <strong>Biotin-Tabs کانینو</strong> با ۱ میلیون میکروگرم بیوتین در کیلوگرم، قویترین دوز موجود در بازار است</li>',
|
||||
'<li><strong>B12 (کوبالامین):</strong> خونسازی و عملکرد عصبی</li>',
|
||||
'</ul>',
|
||||
'',
|
||||
'<h2>نقش بیوتین در سلامت پوشش</h2>',
|
||||
'<p>بیوتین کوآنزیم کربوکسیلازهای مسئول سنتز اسیدهای چرب و گلوکونئوژنز است. <strong>ریزش مو و شکنندگی پنجه</strong> از نشانههای کلاسیک کمبود بیوتین هستند. قرص بیوتین کانینو با تأثیر از هفته چهارم، پوشش براق و پنجههای محکم را تضمین میکند.</p>',
|
||||
'',
|
||||
'<h2>محصولات تقویتکننده پوست و مو</h2>',
|
||||
'<ul>',
|
||||
'<li><strong>Biotin-Tabs:</strong> دوز بالای بیوتین برای ریزش مو</li>',
|
||||
'<li><strong>Lachs-Öl:</strong> روغن سالمون با امگا ۳ (EPA/DHA) ۱۴٪</li>',
|
||||
'<li><strong>Seealgen:</strong> جلبک دریایی ارگانیک برای تقویت رنگدانه</li>',
|
||||
'<li><strong>Marine-Ölmischung PREMIUM:</strong> ترکیب روغن آرگان و گل رز با آنتیاکسیدان قوی</li>',
|
||||
'</ul>',
|
||||
'',
|
||||
'<h2>توصیه تغذیهای</h2>',
|
||||
'<p>برای سلامت پوست و مو، ترکیب <strong>Biotin-Tabs</strong> + <strong>Lachs-Öl</strong> بهترین نتیجه را میدهد. بیوتین از داخل کراتینسازی را تقویت کرده و امگا ۳ التهاب پوستی را کاهش میدهد.</p>'
|
||||
].join('\n');
|
||||
|
||||
const blogs = [
|
||||
{
|
||||
title: 'اهمیت کلسیم در رشد استخوان تولهسگها',
|
||||
slug: 'calcium-importance-for-puppies',
|
||||
content: `<p>کلسیم یکی از مهمترین مواد معدنی برای رشد ساختار استخوانی است. تولهسگها در ماههای اول زندگی به کلسیم بیشتری نیاز دارند تا اسکلتبندی محکمی داشته باشند.</p>
|
||||
|
||||
<h2>نسبت Ca:P (کلسیم به فسفر)</h2>
|
||||
<p>نسبت ایدهآل کلسیم به فسفر در رژیم تولهسگها <strong>۱.۲:۱ تا ۱.۵:۱</strong> است. عدم تعادل در این نسبت میتواند منجر به ناهنجاریهای استخوانی مانند پاهای پرانتزی و دیسپلازی هیپ شود. <strong>پودر پوسته تخم مرغ کانینو</strong> با ۳۶٪ کلسیم خالص و فسفر ۰.۱٪ بهترین گزینه برای تنظیم این نسبت است.</p>
|
||||
|
||||
<h2>نقش ویتامین D3 در جذب کلسیم</h2>
|
||||
<p>ویتامین D3 (کولهکلسیفرول) جذب کلسیم از روده را تا ۳۰٪ افزایش میدهد. <strong>مصرف همزمان ویتامین D2 (ارگوکلسیفرول) با D3 مجاز نیست</strong> زیرا این دو با یکدیگر رقابت کرده و جذب مؤثر را کاهش میدهند.</p>
|
||||
|
||||
<h2>محصولات توصیه شده برای تولهسگها</h2>
|
||||
<ul>
|
||||
<li><strong>CANHYDROX GAG:</strong> از ۸ هفتگی برای استحکام اسکلت و دندانها (۲ قرص به ازای ۱۰kg تا ۱۵ ماهگی)</li>
|
||||
<li><strong>Welpenmilch:</strong> شیر جایگزین مادر از روز اول (غنی از تورین و ویتامینها)</li>
|
||||
<li><strong>Welpenbrei:</strong> فرنی پروتئین بالا از هفته سوم</li>
|
||||
<li><strong>Eierschalenpulver:</strong> پودر پوسته تخم مرغ برای تنظیم نسبت Ca:P</li>
|
||||
</ul>
|
||||
|
||||
<h2>هشدار برای نژادهای بزرگ</h2>
|
||||
<p>نژادهای بزرگ (ژرمن شپرد، لابرادور، گلدن رتریور و ...) مستعد دیسپلازی هیپ و مشکلات مفصلی هستند. مصرف مکملهای ارتوپدی مانند <strong>Flexan</strong> (کلاژن هیدرولیز شده با صدف لب سبز) از دوران تولهگی توصیه میشود.</p>`
|
||||
content: blog1Content,
|
||||
imageUrl: '/images/wiki/calcium.webp',
|
||||
metaTitle: 'اهمیت کلسیم در رشد سگ',
|
||||
metaDescription: 'چرا کلسیم برای توله سگ ها مهم است؟',
|
||||
@ -51,30 +82,7 @@ async function main() {
|
||||
{
|
||||
title: 'ویتامینهای گروه B و سلامت پوست و مو',
|
||||
slug: 'b-vitamins-and-skin-health',
|
||||
content: `<p>ویتامینهای B Complex (B1، B2، B6، B12 و ...) کوآنزیمهای ضروری چرخه کربس هستند که انرژی سلولی (ATP) را تولید میکنند. کمبود این ویتامینها منجر به خستگی مزمن، ریزش مو و التهابات پوستی میشود.</p>
|
||||
|
||||
<h2>هر ویتامین B چه میکند؟</h2>
|
||||
<ul>
|
||||
<li><strong>B1 (تیامین):</strong> متابولیسم کربوهیدراتها و عملکرد سیستم عصبی</li>
|
||||
<li><strong>B2 (ریبوفلاوین):</strong> تولید انرژی و سلامت پوست</li>
|
||||
<li><strong>B6 (پیریدوکسین):</strong> سنتز پروتئین و تولید گلبول قرمز</li>
|
||||
<li><strong>B7 (بیوتین / ویتامین H):</strong> کراتینسازی مو و پنجه - <strong>Biotin-Tabs کانینو</strong> با ۱ میلیون میکروگرم بیوتین در کیلوگرم، قویترین دوز موجود در بازار است</li>
|
||||
<li><strong>B12 (کوبالامین):</strong> خونسازی و عملکرد عصبی</li>
|
||||
</ul>
|
||||
|
||||
<h2>نقش بیوتین در سلامت پوشش</h2>
|
||||
<p>بیوتین کوآنزیم کربوکسیلازهای مسئول سنتز اسیدهای چرب و گلوکونئوژنز است. <strong>ریزش مو و شکنندگی پنجه</strong> از نشانههای کلاسیک کمبود بیوتین هستند. قرص بیوتین کانینو با تأثیر از هفته چهارم، پوشش براق و پنجههای محکم را تضمین میکند.</p>
|
||||
|
||||
<h2>محصولات تقویتکننده پوست و مو</h2>
|
||||
<ul>
|
||||
<li><strong>Biotin-Tabs:</strong> دوز بالای بیوتین برای ریزش مو</li>
|
||||
<li><strong>Lachs-Öl:</strong> روغن سالمون با امگا ۳ (EPA/DHA) ۱۴٪</li>
|
||||
<li><strong>Seealgen:</strong> جلبک دریایی ارگانیک برای تقویت رنگدانه</li>
|
||||
<li><strong>Marine-Ölmischung PREMIUM:</strong> ترکیب روغن آرگان و گل رز با آنتیاکسیدان قوی</li>
|
||||
</ul>
|
||||
|
||||
<h2>توصیه تغذیهای</h2>
|
||||
<p>برای سلامت پوست و مو، ترکیب <strong>Biotin-Tabs</strong> + <strong>Lachs-Öl</strong> بهترین نتیجه را میدهد. بیوتین از داخل کراتینسازی را تقویت کرده و امگا ۳ التهاب پوستی را کاهش میدهد.</p>`
|
||||
content: blog2Content,
|
||||
imageUrl: '/images/wiki/vitamins.webp',
|
||||
metaTitle: 'ویتامین ب در سلامت پت',
|
||||
metaDescription: 'تاثیر ویتامین بی بر پوست و مو',
|
||||
|
||||
@ -70,10 +70,10 @@ async function main() {
|
||||
create: {
|
||||
artNo,
|
||||
name,
|
||||
slug: artNo,
|
||||
scientificTagline,
|
||||
description,
|
||||
shortDescription,
|
||||
category,
|
||||
categorySlug,
|
||||
priceValue,
|
||||
priceDisplay,
|
||||
@ -81,7 +81,8 @@ async function main() {
|
||||
packageSize,
|
||||
dosageLogic,
|
||||
suitableFor,
|
||||
imageUrl
|
||||
imageUrl,
|
||||
category: { connectOrCreate: { where: { slug: categorySlug }, create: { name: category, slug: categorySlug } } }
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ export class ProductsService {
|
||||
take: limit,
|
||||
orderBy: { [sortBy]: sortOrder },
|
||||
include: {
|
||||
ingredients: true,
|
||||
ingredientList: true,
|
||||
symptoms: true,
|
||||
},
|
||||
}),
|
||||
@ -58,7 +58,7 @@ export class ProductsService {
|
||||
return this.prisma.product.findFirst({
|
||||
where: isUuid ? { id: idOrSlug } : { slug: idOrSlug },
|
||||
include: {
|
||||
ingredients: true,
|
||||
ingredientList: true,
|
||||
symptoms: true,
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { X, Upload, Image as ImageIcon, Trash2, CheckCircle2 } from 'lucide-react';
|
||||
import { X, Upload, Image as ImageIcon, Trash2, CheckCircle2, Clipboard } from 'lucide-react';
|
||||
import api from '../../services/api';
|
||||
import Spinner from './Spinner';
|
||||
|
||||
@ -21,7 +21,9 @@ export default function MediaSelector({ isOpen, onClose, onSelect, multiple = fa
|
||||
const [mediaList, setMediaList] = useState<Media[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const [pasteStatus, setPasteStatus] = useState<string | null>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const pasteTimerRef = useRef<ReturnType<typeof setTimeout>>();
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
@ -29,6 +31,44 @@ export default function MediaSelector({ isOpen, onClose, onSelect, multiple = fa
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
|
||||
const handlePaste = (e: ClipboardEvent) => {
|
||||
const items = e.clipboardData?.items;
|
||||
if (!items) return;
|
||||
|
||||
for (const item of Array.from(items)) {
|
||||
if (item.type.startsWith('image/')) {
|
||||
e.preventDefault();
|
||||
setPasteStatus('در حال آپلود از کلیپبورد...');
|
||||
const file = item.getAsFile();
|
||||
if (!file) continue;
|
||||
const formData = new FormData();
|
||||
formData.append('file', file, `clipboard-${Date.now()}.png`);
|
||||
api.post('/admin/media/upload', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
}).then(() => {
|
||||
setPasteStatus('آپلود موفق!');
|
||||
fetchMedia();
|
||||
}).catch(() => {
|
||||
setPasteStatus('خطا در آپلود');
|
||||
}).finally(() => {
|
||||
if (pasteTimerRef.current) clearTimeout(pasteTimerRef.current);
|
||||
pasteTimerRef.current = setTimeout(() => setPasteStatus(null), 3000);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('paste', handlePaste);
|
||||
return () => {
|
||||
document.removeEventListener('paste', handlePaste);
|
||||
if (pasteTimerRef.current) clearTimeout(pasteTimerRef.current);
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
const fetchMedia = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@ -100,8 +140,19 @@ export default function MediaSelector({ isOpen, onClose, onSelect, multiple = fa
|
||||
|
||||
{/* Toolbar */}
|
||||
<div className="p-4 border-b border-gray-100 flex justify-between items-center bg-white">
|
||||
<p className="text-sm font-bold text-gray-600">{mediaList.length} فایل موجود</p>
|
||||
<div>
|
||||
<div className="flex items-center gap-3">
|
||||
<p className="text-sm font-bold text-gray-600">{mediaList.length} فایل موجود</p>
|
||||
{pasteStatus && (
|
||||
<span className={`text-xs px-2 py-1 rounded-full font-medium ${pasteStatus.includes('موفق') ? 'bg-green-100 text-green-700' : pasteStatus.includes('خطا') ? 'bg-red-100 text-red-700' : 'bg-blue-100 text-blue-700'}`}>
|
||||
{pasteStatus}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xs text-gray-400 hidden sm:flex items-center gap-1">
|
||||
<Clipboard className="w-3.5 h-3.5" />
|
||||
Ctrl+V برای چسباندن تصویر
|
||||
</span>
|
||||
<input
|
||||
type="file"
|
||||
ref={fileInputRef}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
"dev:backend": "cd backend && npm run start:dev",
|
||||
"dev:frontend": "cd frontend/application && npm run dev",
|
||||
"dev:admin": "cd frontend/admin-panel && npm run dev",
|
||||
"dev": "concurrently -c \"blue,magenta,green,yellow\" -n \"backend,store,admin,open\" \"npm run dev:backend\" \"npm run dev:frontend\" \"npm run dev:admin\" \"node scripts/open-browsers.js\"",
|
||||
"dev": "node scripts/start-dev.js"
|
||||
"build:backend": "cd backend && npm run build",
|
||||
"build:frontend": "cd frontend/application && npm run build",
|
||||
"build:admin": "cd frontend/admin-panel && npm run build",
|
||||
|
||||
43
scripts/start-dev.js
Normal file
43
scripts/start-dev.js
Normal file
@ -0,0 +1,43 @@
|
||||
const { spawn } = require('child_process');
|
||||
const http = require('http');
|
||||
|
||||
const BACKEND_PORT = 4001;
|
||||
const MAX_WAIT = 60000;
|
||||
|
||||
function waitForBackend(port, timeout) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const start = Date.now();
|
||||
function check() {
|
||||
const req = http.get(`http://127.0.0.1:${port}/api/docs`, (res) => {
|
||||
resolve();
|
||||
});
|
||||
req.on('error', () => {
|
||||
if (Date.now() - start > timeout) {
|
||||
reject(new Error(`Backend did not start within ${timeout/1000}s`));
|
||||
} else {
|
||||
setTimeout(check, 1000);
|
||||
}
|
||||
});
|
||||
req.setTimeout(3000, () => { req.destroy(); });
|
||||
}
|
||||
setTimeout(check, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
const backend = spawn('npm', ['run', 'dev:backend'], { stdio: 'inherit', shell: true });
|
||||
|
||||
waitForBackend(BACKEND_PORT, MAX_WAIT).then(() => {
|
||||
const store = spawn('npm', ['run', 'dev:frontend'], { stdio: 'inherit', shell: true });
|
||||
const admin = spawn('npm', ['run', 'dev:admin'], { stdio: 'inherit', shell: true });
|
||||
|
||||
const openBrowsers = spawn('node', ['scripts/open-browsers.js'], { stdio: 'inherit', shell: true });
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
backend.kill(); store.kill(); admin.kill(); openBrowsers.kill();
|
||||
process.exit();
|
||||
});
|
||||
}).catch((err) => {
|
||||
console.error(err.message);
|
||||
backend.kill();
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user