Rebrand the product to Montact and point local UI at production API.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+1
-1
@@ -17,7 +17,7 @@ SMS_PASSWORD=
|
||||
SMS_SENDER=
|
||||
# SMS_BASE_URL=http://smspanel.Trez.ir
|
||||
# SMS_SEND_PATH=/SendMessageWithCode.ashx
|
||||
# SMS_OTP_TEMPLATE=کد ورود پرتال منتو: {code}
|
||||
# SMS_OTP_TEMPLATE=کد ورود پرتال منتکت: {code}
|
||||
# SMS_TIMEOUT_MS=20000
|
||||
|
||||
# رمزنگاری توکنهای اتصال حسابداری (تهحساب) — ۳۲ بایت بهصورت hex (۶۴ کاراکتر)
|
||||
|
||||
@@ -4,6 +4,7 @@ dist/
|
||||
coverage/
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
*.log
|
||||
.DS_Store
|
||||
apps/api/prisma/*.db
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Mento CRM (منتو)
|
||||
# Montact CRM (منتکت)
|
||||
|
||||
Monorepo برای CRM فروشمحور ایرانی با همترازی قابلیت نسبت به دیدار، بهاضافه Smart Notes (سند کارفرما).
|
||||
|
||||
@@ -13,7 +13,7 @@ Monorepo برای CRM فروشمحور ایرانی با همترازی ق
|
||||
| فایل | MinIO (آماده در Compose) |
|
||||
| لندینگ | بعداً با Nuxt در `apps/landing` |
|
||||
|
||||
UI را پیکسلبهپیکسل از دیدار کپی نمیکنیم؛ هویت بصری مستقل **منتو (Mento)** است.
|
||||
UI را پیکسلبهپیکسل از دیدار کپی نمیکنیم؛ هویت بصری مستقل **منتکت (Montact)** است.
|
||||
|
||||
## ساختار
|
||||
|
||||
@@ -44,27 +44,35 @@ docker compose up -d
|
||||
```bash
|
||||
npm install
|
||||
npm run build -w @mento/shared
|
||||
```
|
||||
|
||||
برای کار روی ظاهر، فقط فرانت را روشن کن. درخواستهای `/api` میروند به سرور اصلی (`https://crm.montzar.ir`) و لازم نیست API لوکال بالا باشد:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
- پنل لوکال: http://localhost:5173
|
||||
- ورود با همان حساب سرور (`crm.montzar.ir`)، نه حساب دمو
|
||||
|
||||
اگر خواستی API را هم روی سیستم خودت داشته باشی:
|
||||
|
||||
```bash
|
||||
npm run prisma:generate -w @mento/api
|
||||
npm run prisma:migrate -w @mento/api
|
||||
npm run prisma:seed -w @mento/api
|
||||
```
|
||||
|
||||
ترمینال ۱:
|
||||
ترمینال ۱: `npm run dev:api`
|
||||
ترمینال ۲: در PowerShell
|
||||
|
||||
```bash
|
||||
npm run dev:api
|
||||
```powershell
|
||||
$env:MENTO_DEV_API="http://127.0.0.1:3000"; npm run dev
|
||||
```
|
||||
|
||||
ترمینال ۲:
|
||||
یا در `apps/web/.env.development.local` بنویس: `MENTO_DEV_API=http://127.0.0.1:3000`
|
||||
|
||||
```bash
|
||||
npm run dev:web
|
||||
```
|
||||
|
||||
- پنل: http://localhost:5173
|
||||
- API: http://localhost:3000/api/health
|
||||
|
||||
حساب دمو بعد از seed:
|
||||
حساب دمو فقط برای API لوکال بعد از seed:
|
||||
|
||||
- مالک: `owner@demo.local` / `demo1234`
|
||||
- مدیر: `admin@demo.local` / `demo1234`
|
||||
@@ -75,10 +83,10 @@ npm run dev:web
|
||||
فاز ۱ هسته فروش/پشتیبانی و Smart Notes عملاً کامل است. جزئیات تیکخورده و باقیمانده:
|
||||
|
||||
- [`docs/وضعیت-پیشرفت.md`](docs/وضعیت-پیشرفت.md) ← **چکلیست زنده**
|
||||
- [`docs/قابلیتهای-منتو.md`](docs/قابلیتهای-منتو.md) ← فهرست کوتاه قابلیتها
|
||||
- [`docs/قابلیتهای-منتکت.md`](docs/قابلیتهای-منتکت.md) ← فهرست کوتاه قابلیتها
|
||||
- [`docs/راهنما-و-نقشه-راه.md`](docs/راهنما-و-نقشه-راه.md) ← نقشه راه
|
||||
|
||||
نمونههای اخیر آمادهشده: احراز هویت OTP/رمز، Ask Mento، Voice-to-Text، اتصال تهحساب، یادآور SMS فعالیت، UI یکدست، جداکننده مبلغ.
|
||||
نمونههای اخیر آمادهشده: احراز هویت OTP/رمز، Ask Montact، Voice-to-Text، اتصال تهحساب، یادآور SMS فعالیت، UI یکدست، جداکننده مبلغ.
|
||||
|
||||
استقرار: [`deploy/DEPLOY.md`](deploy/DEPLOY.md) → `crm.montzar.ir`
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import { brand } from '@mento/shared';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
@@ -20,7 +21,7 @@ async function main() {
|
||||
if (org) {
|
||||
org = await prisma.organization.update({
|
||||
where: { id: org.id },
|
||||
data: { slug: 'demo-mento', name: 'سازمان نمونه منتو' },
|
||||
data: { slug: 'demo-mento', name: `سازمان نمونه ${brand.nameFa}` },
|
||||
});
|
||||
console.log('Renamed demo org slug to demo-mento');
|
||||
}
|
||||
@@ -30,7 +31,7 @@ async function main() {
|
||||
const passwordHash = await bcrypt.hash('demo1234', 10);
|
||||
org = await prisma.organization.create({
|
||||
data: {
|
||||
name: 'سازمان نمونه منتو',
|
||||
name: `سازمان نمونه ${brand.nameFa}`,
|
||||
slug: 'demo-mento',
|
||||
baseCurrency: 'IRR',
|
||||
enabledCurrencies: ['IRR', 'USD', 'EUR'],
|
||||
@@ -747,7 +748,7 @@ async function main() {
|
||||
data: [
|
||||
{
|
||||
organizationId: org.id,
|
||||
name: 'لایسنس سازمانی منتو',
|
||||
name: `لایسنس سازمانی ${brand.nameFa}`,
|
||||
sku: 'MENTO-ORG',
|
||||
unitPrice: 80_000_000,
|
||||
currency: 'IRR',
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
noteAccessWhere,
|
||||
ownerWhere,
|
||||
} from '../common/record-scope';
|
||||
import { BUSINESS_FIELD_LABELS, isBusinessField } from '@mento/shared';
|
||||
import { BUSINESS_FIELD_LABELS, brand, brandSuggestionFa, isBusinessField } from '@mento/shared';
|
||||
import { AiService } from './ai.service';
|
||||
|
||||
type CatalogItem = AskMentoSourceDto;
|
||||
@@ -39,7 +39,7 @@ export class AiAskService {
|
||||
answer?: string;
|
||||
sources?: Array<{ type?: string; id?: string; label?: string }>;
|
||||
}>(
|
||||
`You are Mento, a Persian CRM sales assistant.
|
||||
`You are ${brand.name}, a Persian CRM sales assistant.
|
||||
Answer ONLY from the CRM context. If the context is insufficient, say so in Persian.
|
||||
Do not invent customers, amounts, dates, or promises.
|
||||
Return JSON: { "answer": string, "sources": [{ "type": "person"|"note"|"activity"|"deal"|"memory", "id": string, "label": string }] }
|
||||
@@ -73,7 +73,7 @@ Return JSON: { "text": string, "cta": string, "ctaKind": "call"|"activity" }
|
||||
Rules:
|
||||
- 2 to 4 short sentences from the context only.
|
||||
- Mention durable memory, last contact, open deals, or next follow-up when present.
|
||||
- cta is one concrete next step, starting with «پیشنهاد منتو:».
|
||||
- cta is one concrete next step, starting with «${brand.suggestionPrefixFa}».
|
||||
- ctaKind is "call" if a phone follow-up is best, otherwise "activity".
|
||||
- Do not invent facts.`,
|
||||
`Customer briefing context:\n${context}`,
|
||||
@@ -84,7 +84,7 @@ Rules:
|
||||
const ctaKind = raw.ctaKind === 'activity' ? 'activity' : 'call';
|
||||
return {
|
||||
text: text || 'هنوز دادهٔ کافی برای خلاصه هوشمند این مشتری نیست.',
|
||||
cta: cta || 'پیشنهاد منتو: امروز با مشتری تماس بگیر',
|
||||
cta: cta || brandSuggestionFa('امروز با مشتری تماس بگیر'),
|
||||
ctaKind,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { brand } from '@mento/shared';
|
||||
|
||||
/** پنل ترِز / رایگان — همان الگوی montime */
|
||||
const SMS_TREZ_DEFAULT_BASE_URL = 'http://smspanel.Trez.ir';
|
||||
@@ -44,7 +45,7 @@ export class SmsService {
|
||||
async sendOtpSms(mobile: string, code: string): Promise<boolean> {
|
||||
const template =
|
||||
this.config.get<string>('SMS_OTP_TEMPLATE') ??
|
||||
'کد ورود پرتال منتو: {code}';
|
||||
`کد ورود پرتال ${brand.nameFa}: {code}`;
|
||||
const text = template.replace('{code}', code);
|
||||
const result = await this.sendSmsDetailed(mobile, text);
|
||||
return result.ok;
|
||||
|
||||
@@ -21,6 +21,7 @@ import type { Actor } from '../common/record-scope';
|
||||
import { CompaniesService } from './companies.service';
|
||||
import { CreateCompanyDto, ListQueryDto, UpdateCompanyDto } from './dto';
|
||||
import { ContactsExcelService } from './contacts-excel.service';
|
||||
import { brand } from '@mento/shared';
|
||||
|
||||
@Controller('companies')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@@ -50,7 +51,7 @@ export class CompaniesController {
|
||||
res.set({
|
||||
'Content-Type':
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'Content-Disposition': 'attachment; filename="mento-companies.xlsx"',
|
||||
'Content-Disposition': `attachment; filename="${brand.filePrefix}-companies.xlsx"`,
|
||||
});
|
||||
return new StreamableFile(buffer);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import type { Actor } from '../common/record-scope';
|
||||
import { CreatePersonDto, ListQueryDto, UpdatePersonDto } from './dto';
|
||||
import { PeopleService } from './people.service';
|
||||
import { ContactsExcelService } from './contacts-excel.service';
|
||||
import { brand } from '@mento/shared';
|
||||
|
||||
@Controller('people')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@@ -51,7 +52,7 @@ export class PeopleController {
|
||||
res.set({
|
||||
'Content-Type':
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'Content-Disposition': 'attachment; filename="mento-people.xlsx"',
|
||||
'Content-Disposition': `attachment; filename="${brand.filePrefix}-people.xlsx"`,
|
||||
});
|
||||
return new StreamableFile(buffer);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import type { HealthResponse } from '@mento/shared';
|
||||
import { brand } from '@mento/shared';
|
||||
|
||||
@Controller('health')
|
||||
export class HealthController {
|
||||
@@ -7,7 +8,7 @@ export class HealthController {
|
||||
getHealth(): HealthResponse {
|
||||
return {
|
||||
status: 'ok',
|
||||
service: 'mento-api',
|
||||
service: `${brand.id}-api`,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
UpdateLoyaltyTierDto,
|
||||
} from './dto';
|
||||
import { LoyaltyService } from './loyalty.service';
|
||||
import { brand } from '@mento/shared';
|
||||
|
||||
@Controller('loyalty')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@@ -96,7 +97,7 @@ export class LoyaltyController {
|
||||
res.set({
|
||||
'Content-Type':
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'Content-Disposition': 'attachment; filename="mento-loyalty-members.xlsx"',
|
||||
'Content-Disposition': `attachment; filename="${brand.filePrefix}-loyalty-members.xlsx"`,
|
||||
});
|
||||
return new StreamableFile(buffer);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { brand } from '@mento/shared';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
@@ -24,7 +25,7 @@ async function bootstrap() {
|
||||
const port = Number(config.get<string>('PORT') ?? 3000);
|
||||
await app.listen(port);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Mento API listening on http://localhost:${port}/api`);
|
||||
console.log(`${brand.name} API listening on http://localhost:${port}/api`);
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { brand } from '@mento/shared';
|
||||
import type { NotificationDto, NotificationsListDto } from '@mento/shared';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { SmsService } from '../common/sms.service';
|
||||
@@ -228,7 +229,7 @@ export class NotificationsService {
|
||||
this.logger.warn('Activity reminder SMS skipped: SMS not configured');
|
||||
return;
|
||||
}
|
||||
const text = `یادآوری منتو: ${title}`.slice(0, 160);
|
||||
const text = `${brand.reminderPrefixFa} ${title}`.slice(0, 160);
|
||||
const result = await this.sms.sendSmsDetailed(mobile, text);
|
||||
if (!result.ok) {
|
||||
this.logger.warn(`Activity reminder SMS failed: ${result.reason ?? 'unknown'}`);
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# Mento STT (Pipecat)
|
||||
# Montact STT (Pipecat)
|
||||
|
||||
تبدیل صوت به متن — فقط STT. بدون TTS، بدون مکالمهٔ صوتی.
|
||||
|
||||
|
||||
+2
-1
@@ -3,7 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>منتو | Mento CRM</title>
|
||||
<title>منتکت | Montact CRM</title>
|
||||
<link rel="icon" type="image/png" href="/brand/logo.png" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 148 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 148 KiB |
@@ -4,7 +4,7 @@ import { RouterLink } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
import { api } from '@/lib/api';
|
||||
import UiIcon from '@/components/UiIcon.vue';
|
||||
import type { AskMentoResponse, AskMentoSourceDto } from '@mento/shared';
|
||||
import { brand, type AskMentoResponse, type AskMentoSourceDto } from '@mento/shared';
|
||||
|
||||
const props = defineProps<{
|
||||
personId?: string;
|
||||
@@ -55,7 +55,7 @@ async function submit() {
|
||||
<input
|
||||
v-model="question"
|
||||
type="search"
|
||||
:placeholder="placeholder || 'از منتو بپرس...'"
|
||||
:placeholder="placeholder || brand.askPlaceholderFa"
|
||||
/>
|
||||
<button type="submit" :disabled="asking || question.trim().length < 2">
|
||||
{{ asking ? '…' : 'بپرس' }}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { api } from '@/lib/api';
|
||||
import type { MemoryItemDto, PaginatedResponse } from '@mento/shared';
|
||||
import { brand, type MemoryItemDto, type PaginatedResponse } from '@mento/shared';
|
||||
|
||||
const props = defineProps<{
|
||||
personId: string;
|
||||
@@ -117,7 +117,7 @@ onMounted(load);
|
||||
<section class="memory-panel">
|
||||
<header>
|
||||
<div>
|
||||
<p class="eyebrow">Mento Memory</p>
|
||||
<p class="eyebrow">{{ brand.memoryEn }}</p>
|
||||
<h2>حافظه مشتری</h2>
|
||||
<p class="lead">نکات پایدار درباره این شخص — فقط با تأیید شما ذخیره میشود.</p>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { computed, reactive, ref, watch } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { api } from '@/lib/api';
|
||||
import JalaliDateInput from '@/components/JalaliDateInput.vue';
|
||||
import type { NoteAiPayloadDto, NoteAiSuggestionDto } from '@mento/shared';
|
||||
import { brand, type NoteAiPayloadDto, type NoteAiSuggestionDto } from '@mento/shared';
|
||||
|
||||
const props = defineProps<{
|
||||
noteId: string;
|
||||
@@ -149,7 +149,7 @@ function formatPayloadSummary(value: NoteAiPayloadDto | null) {
|
||||
<template>
|
||||
<section class="ai-panel">
|
||||
<header>
|
||||
<strong>پیشنهاد Mento AI</strong>
|
||||
<strong>پیشنهاد {{ brand.aiName }}</strong>
|
||||
<button
|
||||
v-if="!isPending"
|
||||
type="button"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import MentoLogo from '@/components/brand/MentoLogo.vue';
|
||||
import BrandName from '@/components/brand/BrandName.vue';
|
||||
import { brand } from '@mento/shared';
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
@@ -8,8 +10,7 @@ withDefaults(
|
||||
}>(),
|
||||
{
|
||||
headline: 'همه ارتباطات، یکجا مدیریت میشوند',
|
||||
description:
|
||||
'با منتو فروش، پشتیبانی و ارتباط با مشتری را در یک پنل یکپارچه مدیریت کنید و رشد پایدار بسازید.',
|
||||
description: `با ${brand.nameFa} فروش، پشتیبانی و ارتباط با مشتری را در یک پنل یکپارچه مدیریت کنید و رشد پایدار بسازید.`,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
@@ -19,8 +20,8 @@ withDefaults(
|
||||
<div class="brand-head">
|
||||
<MentoLogo :size="44" :show-text="false" />
|
||||
<div class="brand-copy">
|
||||
<strong>منتو</strong>
|
||||
<span>نرمافزار مدیریت ارتباط با مشتری</span>
|
||||
<BrandName />
|
||||
<span>{{ brand.taglineFa }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { brand } from '@mento/shared';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
lang?: 'fa' | 'en' | 'full';
|
||||
}>(),
|
||||
{ lang: 'fa' },
|
||||
);
|
||||
|
||||
const text = computed(() => {
|
||||
if (props.lang === 'en') return brand.name;
|
||||
if (props.lang === 'full') return brand.fullName;
|
||||
return brand.nameFa;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="brand-name">{{ text }}</span>
|
||||
</template>
|
||||
@@ -1,4 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { brand } from '@mento/shared';
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number;
|
||||
@@ -25,7 +27,7 @@ withDefaults(
|
||||
<img
|
||||
class="bot-img"
|
||||
src="/brand/mento-ai-avatar.png"
|
||||
alt="دستیار هوشمند منتو"
|
||||
:alt="brand.aiNameFa"
|
||||
:width="size"
|
||||
:height="size"
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { brand } from '@mento/shared';
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number;
|
||||
@@ -14,38 +16,22 @@ withDefaults(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mento-logo" :class="variant">
|
||||
<span
|
||||
<div class="brand-logo" :class="variant">
|
||||
<img
|
||||
class="mark"
|
||||
:style="{ width: `${size}px`, height: `${size}px`, borderRadius: `${Math.round(size * 0.3)}px` }"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="14" y="16" width="8" height="32" rx="4" fill="#fff" />
|
||||
<path
|
||||
d="M17 17.5c1-1.6 3.5-1.8 4.8-.5L36 30l4.3-4c1.4-1.3 3.6-1.2 4.7.4L44 27.5 33 37.5c-1.1 1-2.8 1-3.9 0L16.5 24.5 17 17.5Z"
|
||||
fill="#134e4a"
|
||||
:src="brand.logo"
|
||||
:alt="brand.name"
|
||||
:style="{ width: `${size}px`, height: `${size}px` }"
|
||||
/>
|
||||
<path
|
||||
d="M27.5 32 38 21.5c1.3-1.3 3.4-1.2 4.6.2L50 29.5l-2.6 2.6L40 25.5 31 34c-1 1-2.8 1-3.8-.1l-.7-.9Z"
|
||||
fill="#ecfdf5"
|
||||
/>
|
||||
<rect x="42" y="16" width="8" height="32" rx="4" fill="#134e4a" />
|
||||
<path
|
||||
d="M41 17.5c1.3-1.4 3.5-1.3 4.7.2l1.6 1.8-7 6.6-2.1-2c-1.2-1.1-.9-3.2 2.8-6.6Z"
|
||||
fill="#ecfdf5"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<div v-if="showText && variant === 'full'" class="text">
|
||||
<strong>Mento</strong>
|
||||
<small>by Mentech</small>
|
||||
<strong>{{ brand.name }}</strong>
|
||||
<small>by {{ brand.company }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.mento-logo {
|
||||
.brand-logo {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.7rem;
|
||||
@@ -53,20 +39,10 @@ withDefaults(
|
||||
}
|
||||
|
||||
.mark {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex-shrink: 0;
|
||||
background: linear-gradient(145deg, #5eead4, #14b8a6);
|
||||
box-shadow: 0 8px 18px rgba(20, 184, 166, 0.28);
|
||||
overflow: hidden;
|
||||
padding: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.mark svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
object-fit: contain;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.text {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink } from 'vue-router';
|
||||
import UiIcon from '@/components/UiIcon.vue';
|
||||
import type { AskMentoSourceDto } from '@mento/shared';
|
||||
import { brand, type AskMentoSourceDto } from '@mento/shared';
|
||||
|
||||
defineProps<{
|
||||
askQuery: string;
|
||||
@@ -41,7 +41,7 @@ function pick(label: string) {
|
||||
<header class="panel-head">
|
||||
<h2>
|
||||
<span class="spark"><UiIcon name="sparkle" :size="15" /></span>
|
||||
دستیار هوشمند منتو
|
||||
{{ brand.aiNameFa }}
|
||||
</h2>
|
||||
</header>
|
||||
|
||||
@@ -79,7 +79,7 @@ function pick(label: string) {
|
||||
<input
|
||||
:value="askQuery"
|
||||
type="search"
|
||||
placeholder="از منتو بپرس..."
|
||||
:placeholder="brand.askPlaceholderFa"
|
||||
:disabled="asking"
|
||||
@input="emit('update:askQuery', ($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { computed } from 'vue';
|
||||
import { RouterLink } from 'vue-router';
|
||||
import UiIcon from '@/components/UiIcon.vue';
|
||||
import MentoAiAvatar from '@/components/brand/MentoAiAvatar.vue';
|
||||
import type { HomeDashboardAlertDto } from '@mento/shared';
|
||||
import { brand, type HomeDashboardAlertDto } from '@mento/shared';
|
||||
|
||||
const props = defineProps<{
|
||||
greeting: string;
|
||||
@@ -91,7 +91,7 @@ function alertIcon(tone: string) {
|
||||
</span>
|
||||
<div class="greet-text">
|
||||
<h1>{{ greeting }} {{ firstName }} 👋</h1>
|
||||
<p>منتو اینا رو برات پیدا کرده</p>
|
||||
<p>{{ brand.nameFa }} اینا رو برات پیدا کرده</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -116,7 +116,7 @@ function alertIcon(tone: string) {
|
||||
<input
|
||||
:value="askQuery"
|
||||
type="search"
|
||||
placeholder="از منتو بپرس..."
|
||||
:placeholder="brand.askPlaceholderFa"
|
||||
@input="emit('update:askQuery', ($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
<kbd>⌘K</kbd>
|
||||
|
||||
@@ -7,11 +7,13 @@ import { formatJalaliShort, toFaDigits } from '@/lib/jalali';
|
||||
import UiIcon from '@/components/UiIcon.vue';
|
||||
import MentoLogo from '@/components/brand/MentoLogo.vue';
|
||||
import QuickCreate from '@/components/QuickCreate.vue';
|
||||
import BrandName from '@/components/brand/BrandName.vue';
|
||||
import type {
|
||||
ActivitySummaryDto,
|
||||
NotificationDto,
|
||||
NotificationsListDto,
|
||||
} from '@mento/shared';
|
||||
import { brand } from '@mento/shared';
|
||||
import { USER_ROLE_LABELS, canManageSettings, canViewAllRecords } from '@/lib/roles';
|
||||
|
||||
const auth = useAuthStore();
|
||||
@@ -366,7 +368,7 @@ onUnmounted(() => {
|
||||
<UiIcon name="sparkle" :size="18" />
|
||||
</span>
|
||||
<p>
|
||||
<span>با منتو فروشات را</span>
|
||||
<span>با {{ brand.nameFa }} فروشات را</span>
|
||||
<span>چند برابر کن</span>
|
||||
</p>
|
||||
</div>
|
||||
@@ -389,7 +391,7 @@ onUnmounted(() => {
|
||||
</button>
|
||||
<div class="top-brand">
|
||||
<MentoLogo :size="32" :show-text="false" variant="mark" />
|
||||
<strong>Mento</strong>
|
||||
<BrandName lang="en" />
|
||||
</div>
|
||||
<button class="round-btn" type="button" aria-label="اعلانها" @click="toggleNotif">
|
||||
<UiIcon name="bell" :size="18" />
|
||||
@@ -518,45 +520,6 @@ onUnmounted(() => {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
.mark {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 12px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: linear-gradient(145deg, #2ec4a8, #14b8a6);
|
||||
color: #fff;
|
||||
font-weight: 800;
|
||||
font-size: 1.05rem;
|
||||
box-shadow: 0 8px 18px rgba(20, 184, 166, 0.28);
|
||||
}
|
||||
|
||||
.mark.sm {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 10px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.brand strong {
|
||||
display: block;
|
||||
font-size: 1.05rem;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.brand small {
|
||||
display: block;
|
||||
color: #94a3b8;
|
||||
font-size: 0.72rem;
|
||||
margin-top: 0.05rem;
|
||||
}
|
||||
|
||||
.side-nav {
|
||||
display: grid;
|
||||
gap: 0.45rem;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MemoryItemDto, NoteDto, PersonDto } from '@mento/shared';
|
||||
import { brand, brandSuggestionFa, type MemoryItemDto, type NoteDto, type PersonDto } from '@mento/shared';
|
||||
|
||||
export type ProbabilityBand = 'بالا' | 'متوسط' | 'پایین';
|
||||
|
||||
@@ -85,12 +85,12 @@ export function buildSmartSummary(input: {
|
||||
const hasSignal = memoryBits.length > 0 || input.notes.length > 0 || input.lastQuoteLabel;
|
||||
const text = hasSignal
|
||||
? parts.join(' ').replace(/\s+/g, ' ').trim()
|
||||
: 'هنوز خلاصهٔ پایداری از این مشتری ثبت نشده. با یادداشت یا حافظهٔ منتو، پیشنهاد هوشمند اینجا ظاهر میشود.';
|
||||
: `هنوز خلاصهٔ پایداری از این مشتری ثبت نشده. با یادداشت یا حافظهٔ ${brand.nameFa}، پیشنهاد هوشمند اینجا ظاهر میشود.`;
|
||||
|
||||
let cta = 'پیشنهاد منتو: امروز با مشتری تماس بگیر';
|
||||
let cta = brandSuggestionFa('امروز با مشتری تماس بگیر');
|
||||
let ctaKind: 'call' | 'activity' = 'call';
|
||||
if (input.daysSinceCall != null && input.daysSinceCall <= 1) {
|
||||
cta = 'پیشنهاد منتو: پیگیری بعدی را ثبت کن';
|
||||
cta = brandSuggestionFa('پیگیری بعدی را ثبت کن');
|
||||
ctaKind = 'activity';
|
||||
}
|
||||
return { text, cta, ctaKind };
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import { brand } from '@mento/shared';
|
||||
import App from './App.vue';
|
||||
import { router } from './router';
|
||||
import './styles/main.css';
|
||||
|
||||
document.title = `${brand.nameFa} | ${brand.fullName}`;
|
||||
const favicon = document.querySelector<HTMLLinkElement>('link[rel="icon"]');
|
||||
if (favicon) favicon.href = brand.logo;
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(createPinia());
|
||||
app.use(router);
|
||||
|
||||
@@ -15,6 +15,7 @@ import JalaliDateInput from '@/components/JalaliDateInput.vue';
|
||||
import VoiceToTextButton from '@/components/VoiceToTextButton.vue';
|
||||
import { appendSpokenText } from '@/lib/spoken-text';
|
||||
import UiIcon from '@/components/UiIcon.vue';
|
||||
import { brand } from '@mento/shared';
|
||||
import UserAvatar from '@/components/UserAvatar.vue';
|
||||
import type {
|
||||
ActivityDto,
|
||||
@@ -498,7 +499,7 @@ watch([id, isNew], () => void load(), { immediate: true });
|
||||
<input v-model="reminderNotifyApp" type="checkbox" />
|
||||
<span>
|
||||
<strong>اعلان داخل پنل</strong>
|
||||
<small>در زنگوله اعلانهای منتو نشان داده میشود</small>
|
||||
<small>در زنگوله اعلانهای {{ brand.nameFa }} نشان داده میشود</small>
|
||||
</span>
|
||||
</label>
|
||||
<label class="channel">
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
PersonDto,
|
||||
PersonMood,
|
||||
} from '@mento/shared';
|
||||
import { brand } from '@mento/shared';
|
||||
import { PERSON_MOOD_LABELS, PERSON_MOODS } from '@/lib/person-mood';
|
||||
|
||||
type Tab = 'people' | 'companies';
|
||||
@@ -178,7 +179,7 @@ async function exportExcel() {
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download =
|
||||
tab.value === 'people' ? 'mento-people.xlsx' : 'mento-companies.xlsx';
|
||||
tab.value === 'people' ? `${brand.filePrefix}-people.xlsx` : `${brand.filePrefix}-companies.xlsx`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
} catch {
|
||||
|
||||
@@ -12,6 +12,8 @@ import { formatLocalDateInput, toFaDigits } from '@/lib/jalali';
|
||||
import JalaliDateInput from '@/components/JalaliDateInput.vue';
|
||||
import MoneyInput from '@/components/MoneyInput.vue';
|
||||
import UiIcon from '@/components/UiIcon.vue';
|
||||
import BrandName from '@/components/brand/BrandName.vue';
|
||||
import { brand } from '@mento/shared';
|
||||
import type {
|
||||
CreateInvoiceRequest,
|
||||
CreatePaymentRequest,
|
||||
@@ -763,7 +765,8 @@ onMounted(load);
|
||||
|
||||
<form v-else class="form" @submit.prevent="save">
|
||||
<div class="print-banner">
|
||||
<strong>منتو</strong>
|
||||
<img :src="brand.logo" :alt="brand.name" class="print-logo" />
|
||||
<BrandName />
|
||||
<span v-if="number">{{ number }}</span>
|
||||
</div>
|
||||
|
||||
@@ -2357,12 +2360,21 @@ h1 {
|
||||
|
||||
.print-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding-bottom: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.print-logo {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.page {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import AuthBrandPanel from '@/components/auth/AuthBrandPanel.vue';
|
||||
import MentoLogo from '@/components/brand/MentoLogo.vue';
|
||||
import axios from 'axios';
|
||||
import type { LoginChallengeResponse } from '@mento/shared';
|
||||
import { brand } from '@mento/shared';
|
||||
import BrandName from '@/components/brand/BrandName.vue';
|
||||
|
||||
const auth = useAuthStore();
|
||||
const router = useRouter();
|
||||
@@ -111,8 +113,8 @@ function backToOtpMobile() {
|
||||
<section class="form-panel">
|
||||
<header class="mobile-brand">
|
||||
<MentoLogo :size="52" :show-text="false" />
|
||||
<strong>منتو</strong>
|
||||
<span>نرمافزار مدیریت ارتباط با مشتری</span>
|
||||
<BrandName />
|
||||
<span>{{ brand.taglineFa }}</span>
|
||||
</header>
|
||||
|
||||
<div class="form-inner">
|
||||
@@ -260,7 +262,7 @@ function backToOtpMobile() {
|
||||
</div>
|
||||
|
||||
<footer class="auth-footer">
|
||||
<p>۱۴۰۵ منتو. تمامی حقوق محفوظ است.</p>
|
||||
<p>۱۴۰۵ {{ brand.nameFa }}. تمامی حقوق محفوظ است.</p>
|
||||
<nav>
|
||||
<a href="#">حریم خصوصی</a>
|
||||
<span>|</span>
|
||||
|
||||
@@ -18,6 +18,7 @@ import type {
|
||||
LoyaltyStatsDto,
|
||||
LoyaltyTierDto,
|
||||
} from '@mento/shared';
|
||||
import { brand } from '@mento/shared';
|
||||
|
||||
type MobileTab = 'levels' | 'members' | 'rewards';
|
||||
type Modal =
|
||||
@@ -416,7 +417,7 @@ async function exportExcel() {
|
||||
const url = URL.createObjectURL(data);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'mento-loyalty-members.xlsx';
|
||||
a.download = `${brand.filePrefix}-loyalty-members.xlsx`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
} catch {
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import UiIcon from '@/components/UiIcon.vue';
|
||||
import CustomerMoodFace from '@/components/CustomerMoodFace.vue';
|
||||
import MentoAiAvatar from '@/components/brand/MentoAiAvatar.vue';
|
||||
import { brand } from '@mento/shared';
|
||||
import MentoMemoryPanel from '@/components/MentoMemoryPanel.vue';
|
||||
import AskMentoPanel from '@/components/AskMentoPanel.vue';
|
||||
import ProfileEmptyState from '@/components/person-profile/ProfileEmptyState.vue';
|
||||
@@ -846,7 +847,7 @@ watch(() => route.query.tab, readTab, { immediate: true });
|
||||
<div>
|
||||
<h2>
|
||||
<UiIcon name="sparkle" :size="14" />
|
||||
خلاصه هوشمند منتو
|
||||
خلاصه هوشمند {{ brand.nameFa }}
|
||||
</h2>
|
||||
<p>{{ shownSmart.text }}</p>
|
||||
</div>
|
||||
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
PortalRequestOtpResponse,
|
||||
PortalTicketDto,
|
||||
} from '@mento/shared';
|
||||
import { brand } from '@mento/shared';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@@ -269,7 +270,7 @@ watch(slug, () => {
|
||||
<div class="portal">
|
||||
<header class="top">
|
||||
<div>
|
||||
<p class="brand">منتو · پرتال مشتری</p>
|
||||
<p class="brand">{{ brand.nameFa }} · پرتال مشتری</p>
|
||||
<h1>{{ org?.name || '…' }}</h1>
|
||||
</div>
|
||||
<button v-if="person" class="linkish" type="button" @click="logout">
|
||||
|
||||
@@ -4,6 +4,7 @@ import { RouterLink, useRoute, useRouter } from 'vue-router';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { api } from '@/lib/api';
|
||||
import type { AskMentoResponse } from '@mento/shared';
|
||||
import { brand, brandSuggestionFa } from '@mento/shared';
|
||||
import { formatCompactMoney } from '@/lib/money';
|
||||
import {
|
||||
calendarDaysBetween,
|
||||
@@ -145,9 +146,9 @@ const smartText = computed(() => {
|
||||
|
||||
const smartCta = computed(() => {
|
||||
if (daysSinceContact.value != null && daysSinceContact.value <= 1) {
|
||||
return 'پیشنهاد منتو: پیگیری بعدی را ثبت کن';
|
||||
return brandSuggestionFa('پیگیری بعدی را ثبت کن');
|
||||
}
|
||||
return 'پیشنهاد منتو: امروز با مشتری تماس بگیر';
|
||||
return brandSuggestionFa('امروز با مشتری تماس بگیر');
|
||||
});
|
||||
|
||||
const kpis = computed(() => [
|
||||
@@ -377,12 +378,12 @@ watch(() => auth.user?.id, () => void load(), { immediate: true });
|
||||
<div>
|
||||
<h2>
|
||||
<UiIcon name="sparkle" :size="14" />
|
||||
خلاصه هوشمند منتو
|
||||
خلاصه هوشمند {{ brand.nameFa }}
|
||||
</h2>
|
||||
<p>{{ smartText }}</p>
|
||||
</div>
|
||||
</header>
|
||||
<AskMentoPanel placeholder="از منتو درباره کار امروز بپرس..." />
|
||||
<AskMentoPanel :placeholder="`${brand.askLabelFa} درباره کار امروز...`" />
|
||||
<button class="smart-cta" type="button" @click="onSmartCta">
|
||||
<span>{{ smartCta }}</span>
|
||||
<UiIcon name="chevron" :size="16" />
|
||||
|
||||
@@ -6,7 +6,8 @@ import { BUSINESS_FIELDS, BUSINESS_FIELD_LABELS } from '@/lib/business-fields';
|
||||
import AuthBrandPanel from '@/components/auth/AuthBrandPanel.vue';
|
||||
import MentoLogo from '@/components/brand/MentoLogo.vue';
|
||||
import axios from 'axios';
|
||||
import type { RegisterChallengeResponse } from '@mento/shared';
|
||||
import { brand, type RegisterChallengeResponse } from '@mento/shared';
|
||||
import BrandName from '@/components/brand/BrandName.vue';
|
||||
|
||||
const auth = useAuthStore();
|
||||
const router = useRouter();
|
||||
@@ -106,15 +107,15 @@ function backToForm() {
|
||||
<div class="auth-shell">
|
||||
<AuthBrandPanel
|
||||
headline="همه ارتباطات، یکجا مدیریت میشوند"
|
||||
description="با ساخت حساب منتو، سازمان خود را راهاندازی کنید و فروش و پشتیبانی را از یکجا پیش ببرید."
|
||||
:description="`با ساخت حساب ${brand.nameFa}، سازمان خود را راهاندازی کنید و فروش و پشتیبانی را از یکجا پیش ببرید.`"
|
||||
/>
|
||||
|
||||
<section class="form-panel">
|
||||
<header class="mobile-brand">
|
||||
<RouterLink class="back-link" to="/login">بازگشت</RouterLink>
|
||||
<MentoLogo :size="48" :show-text="false" />
|
||||
<strong>منتو</strong>
|
||||
<span>نرمافزار مدیریت ارتباط با مشتری</span>
|
||||
<BrandName />
|
||||
<span>{{ brand.taglineFa }}</span>
|
||||
</header>
|
||||
|
||||
<div class="form-inner">
|
||||
@@ -329,7 +330,7 @@ function backToForm() {
|
||||
</div>
|
||||
|
||||
<footer class="auth-footer">
|
||||
<p>۱۴۰۵ منتو. تمامی حقوق محفوظ است.</p>
|
||||
<p>۱۴۰۵ {{ brand.nameFa }}. تمامی حقوق محفوظ است.</p>
|
||||
<nav>
|
||||
<a href="#">پشتیبانی</a>
|
||||
<span>|</span>
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import JalaliDateInput from '@/components/JalaliDateInput.vue';
|
||||
import UiIcon from '@/components/UiIcon.vue';
|
||||
import type { OrganizationDto, ReportsDashboardDto } from '@mento/shared';
|
||||
import { brand } from '@mento/shared';
|
||||
|
||||
type PeriodKey = 'today' | 'week' | 'month' | 'quarter' | 'year' | 'custom';
|
||||
|
||||
@@ -137,7 +138,7 @@ function exportReport() {
|
||||
if (!data.value) return;
|
||||
const d = data.value;
|
||||
const lines: string[] = [
|
||||
'گزارشات منتو',
|
||||
`گزارشات ${brand.nameFa}`,
|
||||
`بازه,${rangeLabel()}`,
|
||||
'',
|
||||
'شاخص,مقدار,تغییر٪',
|
||||
@@ -170,7 +171,7 @@ function exportReport() {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `mento-report-${from.value || 'from'}-${to.value || 'to'}.csv`;
|
||||
a.download = `${brand.filePrefix}-report-${from.value || 'from'}-${to.value || 'to'}.csv`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
TahesabConnectionDto,
|
||||
UpdateTahesabConnectionRequest,
|
||||
} from '@mento/shared';
|
||||
import { brand } from '@mento/shared';
|
||||
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
@@ -315,7 +316,7 @@ async function importBulk(mode: 'results' | 'range') {
|
||||
}
|
||||
if (
|
||||
!confirm(
|
||||
`${codes.length} مشتری جدید از تهحساب بهعنوان شخص در منتو ساخته و لینک میشوند. ادامه؟`,
|
||||
`${codes.length} مشتری جدید از تهحساب بهعنوان شخص در ${brand.nameFa} ساخته و لینک میشوند. ادامه؟`,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
@@ -327,7 +328,7 @@ async function importBulk(mode: 'results' | 'range') {
|
||||
if (!fromRaw && !toRaw) {
|
||||
if (
|
||||
!confirm(
|
||||
'همهٔ مشتریان تهحساب وارد منتو میشوند (قبلاً لینکشدهها رد میشوند). ممکن است کمی طول بکشد. ادامه؟',
|
||||
`همهٔ مشتریان تهحساب وارد ${brand.nameFa} میشوند (قبلاً لینکشدهها رد میشوند). ممکن است کمی طول بکشد. ادامه؟`,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
@@ -342,7 +343,7 @@ async function importBulk(mode: 'results' | 'range') {
|
||||
}
|
||||
if (
|
||||
!confirm(
|
||||
`همهٔ مشتریان تهحساب با کد ${fromCode} تا ${toCode} وارد منتو میشوند (قبلاً لینکشدهها رد میشوند). ادامه؟`,
|
||||
`همهٔ مشتریان تهحساب با کد ${fromCode} تا ${toCode} وارد ${brand.nameFa} میشوند (قبلاً لینکشدهها رد میشوند). ادامه؟`,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
|
||||
+16
-3
@@ -1,12 +1,23 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import { defineConfig, loadEnv } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import { fileURLToPath, URL } from 'node:url';
|
||||
|
||||
export default defineConfig({
|
||||
const PROD_API = 'https://crm.montzar.ir';
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '');
|
||||
const apiTarget = env.MENTO_DEV_API || process.env.MENTO_DEV_API || PROD_API;
|
||||
const usingLocalApi = apiTarget.includes('127.0.0.1') || apiTarget.includes('localhost');
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[montact] UI → ${apiTarget}${usingLocalApi ? ' (API لوکال)' : ' (سرور اصلی)'}`);
|
||||
|
||||
return {
|
||||
plugins: [vue()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'@mento/shared': fileURLToPath(new URL('../../packages/shared/src/index.ts', import.meta.url)),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
@@ -14,9 +25,11 @@ export default defineConfig({
|
||||
port: 5173,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:3000',
|
||||
target: apiTarget,
|
||||
changeOrigin: true,
|
||||
secure: apiTarget.startsWith('https://'),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ SMS_PASSWORD=
|
||||
SMS_SENDER=
|
||||
SMS_BASE_URL=http://smspanel.Trez.ir
|
||||
SMS_SEND_PATH=/SendMessageWithCode.ashx
|
||||
SMS_OTP_TEMPLATE=کد ورود پرتال منتو: {code}
|
||||
SMS_OTP_TEMPLATE=کد ورود پرتال منتکت: {code}
|
||||
# SMS_TIMEOUT_MS=20000
|
||||
|
||||
# AI — GapGPT (OpenAI-compatible)
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# استقرار منتو CRM — `crm.montzar.ir`
|
||||
# استقرار منتکت CRM — `crm.montzar.ir`
|
||||
|
||||
> **قفلشده:** فقط همین فایل منبع دستورات استقرار است. پکیجمنیجر = **npm** (نه pnpm/yarn).
|
||||
> قانون Cursor: `.cursor/rules/deploy.mdc`
|
||||
|
||||
+7
-7
@@ -34,25 +34,25 @@ PRESETS = {
|
||||
"roadmap": {
|
||||
"md": "راهنما-و-نقشه-راه.md",
|
||||
"docx": "راهنما-و-نقشه-راه.docx",
|
||||
"header": "راهنما و نقشه راه منتو (Mento CRM)",
|
||||
"footer": "نقشه راه منتو | نسخه ۱.۰ | وضعیت زنده محصول",
|
||||
"header": "راهنما و نقشه راه منتکت (Montact CRM)",
|
||||
"footer": "نقشه راه منتکت | نسخه ۱.۰ | وضعیت زنده محصول",
|
||||
},
|
||||
"user-guide": {
|
||||
"md": "راهنمای-کاربری.md",
|
||||
"docx": "راهنمای-کاربری.docx",
|
||||
"header": "راهنمای کاربری منتو (Mento CRM)",
|
||||
"footer": "راهنمای کاربری منتو | آموزش گامبهگام برای همه همکاران",
|
||||
"header": "راهنمای کاربری منتکت (Montact CRM)",
|
||||
"footer": "راهنمای کاربری منتکت | آموزش گامبهگام برای همه همکاران",
|
||||
},
|
||||
"features": {
|
||||
"md": "قابلیتهای-منتو.md",
|
||||
"md": "قابلیتهای-منتکت.md",
|
||||
"docx": "قابلیتهای-منتو.docx",
|
||||
"header": "قابلیتهای منتو (Mento CRM)",
|
||||
"header": "قابلیتهای منتکت (Montact CRM)",
|
||||
"footer": "فهرست قابلیتهای محصول | تیتر + توضیح مختصر",
|
||||
},
|
||||
"progress": {
|
||||
"md": "وضعیت-پیشرفت.md",
|
||||
"docx": "وضعیت-پیشرفت.docx",
|
||||
"header": "وضعیت پیشرفت منتو (Mento CRM)",
|
||||
"header": "وضعیت پیشرفت منتکت (Montact CRM)",
|
||||
"footer": "چکلیست زنده کارها | سند وضعیت پیشرفت",
|
||||
},
|
||||
}
|
||||
|
||||
+10
-10
@@ -1,19 +1,19 @@
|
||||
# راهنما و نقشه راه منتو (Mento CRM)
|
||||
# راهنما و نقشه راه منتکت (Montact CRM)
|
||||
|
||||
نسخه: ۱.۰ — وضعیت زنده محصول
|
||||
تاریخ بهروزرسانی: ۱۴ شهریور ۱۴۰۵
|
||||
محصول: CRM فروشمحور فارسی **منتو**
|
||||
محصول: CRM فروشمحور فارسی **منتکت**
|
||||
استک: Vue 3 + NestJS + PostgreSQL (Prisma) | ساختار: monorepo
|
||||
محیط عملیاتی: `https://crm.montzar.ir`
|
||||
|
||||
این سند دیگر «پیشنویس قبل از کدنویسی» نیست؛ خلاصهٔ دامنه، فلسفه، آنچه ساخته شده، و مسیر بعدی است.
|
||||
چکلیست روزبهروز: [`وضعیت-پیشرفت.md`](وضعیت-پیشرفت.md) · فهرست قابلیتها: [`قابلیتهای-منتو.md`](قابلیتهای-منتو.md)
|
||||
چکلیست روزبهروز: [`وضعیت-پیشرفت.md`](وضعیت-پیشرفت.md) · فهرست قابلیتها: [`قابلیتهای-منتکت.md`](قابلیتهای-منتکت.md)
|
||||
|
||||
---
|
||||
|
||||
## ۱. هدف محصول
|
||||
|
||||
منتو یک CRM فروش و پشتیبانی برای کسبوکارهای ایرانی است با این حلقه اصلی:
|
||||
منتکت یک CRM فروش و پشتیبانی برای کسبوکارهای ایرانی است با این حلقه اصلی:
|
||||
|
||||
```text
|
||||
سرنخ → شخص / شرکت → معامله در کاریز → فعالیت / Smart Notes → فروش مالی سبک → پسازفروش
|
||||
@@ -22,7 +22,7 @@
|
||||
الهام اولیه از محصولات ایرانی مثل دیدار بوده، ولی هویت بصری و تمایز محصول مستقل است:
|
||||
|
||||
- سادگی کار روزانه فروش و پشتیبانی
|
||||
- پرونده یکپارچه مشتری + حافظه هوشمند (Mento Memory / Ask Mento)
|
||||
- پرونده یکپارچه مشتری + حافظه هوشمند (Montact Memory / Ask Montact)
|
||||
- RTL، تاریخ جلالی، پیامک، و اتصال به حسابداری طلا/جواهر (تهحساب)
|
||||
- نصب اختصاصی (self-host) بهجای وابستگی اجباری به SaaS عمومی
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
| پنل | Vue 3 + Vite + Pinia |
|
||||
| API | NestJS |
|
||||
| دیتابیس | PostgreSQL + Prisma |
|
||||
| UI | زبان بصری منتو (تیل، پنل سفید نرم، موبایل ≤۹۰۰) — نه کپی پیکسلی دیدار |
|
||||
| UI | زبان بصری منتکت (تیل، پنل سفید نرم، موبایل ≤۹۰۰) — نه کپی پیکسلی دیدار |
|
||||
| فایل | فعلاً دیسک محلی؛ Compose برای MinIO آماده است |
|
||||
| لندینگ بازاریابی | Nuxt جدا، بعد از ثبات محصول |
|
||||
| اپ بومی | عمداً خارج از فاز ۱ |
|
||||
@@ -50,7 +50,7 @@
|
||||
| مایلاستون | وضعیت | محتوا |
|
||||
|---|---|---|
|
||||
| **۱-الف** هسته فروش | انجام شده | مخاطب، کاریز، فعالیت، تقویم، اکسل، نقش، چندارزی، UI |
|
||||
| **۱-ب** Smart Notes / AI | تقریباً کامل | Composer، Memory، Ask Mento، Voice-to-Text؛ باقی: Note شخصی/تیمی، تاریخچه ویرایش |
|
||||
| **۱-ب** Smart Notes / AI | تقریباً کامل | Composer، Memory، Ask Montact، Voice-to-Text؛ باقی: Note شخصی/تیمی، تاریخچه ویرایش |
|
||||
| **۱-ج** سرنخ و مالی | انجام شده | فرمساز، پیشفاکتور، فاکتور/اقساط، هدف فروش |
|
||||
| **۱-د** پسازفروش | انجام شده | تیکت، پرتال OTP، پروژه، باشگاه، تهحساب |
|
||||
| **۱-هـ** گزارش و اتصال | نیمهکاره | گزارش و اتوماسیون و SMS عملیاتی آماده؛ API عمومی/وبهوک/درگاه هنوز نه |
|
||||
@@ -61,10 +61,10 @@
|
||||
|
||||
## ۴. تمایز نسبت به CRMهای عمومی
|
||||
|
||||
| حوزه | منتو |
|
||||
| حوزه | منتکت |
|
||||
|---|---|
|
||||
| یادداشت | Smart Notes + پیشنهاد اقدام با تأیید + Memory با منبع |
|
||||
| پرسش | Ask Mento روی پرونده و یادداشتها |
|
||||
| پرسش | Ask Montact روی پرونده و یادداشتها |
|
||||
| صدا | دیکته Voice-to-Text در فرمها |
|
||||
| ایران | جلالی، RTL، OTP/پیامک، چندارزی ریالمحور |
|
||||
| طلا و جواهر | حوزه کسبوکار + اتصال تهحساب (مانده و سند) |
|
||||
@@ -111,7 +111,7 @@
|
||||
|---|---|
|
||||
| [`وضعیت-پیشرفت.md`](وضعیت-پیشرفت.md) | چکلیست زنده |
|
||||
| [`راهنمای-کاربری.md`](راهنمای-کاربری.md) | آموزش کاربران |
|
||||
| [`قابلیتهای-منتو.md`](قابلیتهای-منتو.md) | فهرست کوتاه قابلیتها |
|
||||
| [`قابلیتهای-منتکت.md`](قابلیتهای-منتکت.md) | فهرست کوتاه قابلیتها |
|
||||
| [`integrations/tahesab.md`](integrations/tahesab.md) | اتصال تهحساب |
|
||||
| [`../deploy/DEPLOY.md`](../deploy/DEPLOY.md) | استقرار production |
|
||||
| [`client/MENT_Smart_Notes_extracted.md`](client/MENT_Smart_Notes_extracted.md) | PRD اولیه Smart Notes |
|
||||
|
||||
Binary file not shown.
+10
-10
@@ -1,16 +1,16 @@
|
||||
# راهنمای کاربری منتو (Mento CRM)
|
||||
# راهنمای کاربری منتکت (Montact CRM)
|
||||
|
||||
**نسخه راهنما:** هماهنگ با محصول روی `https://crm.montzar.ir` (بهروز ۱۴ شهریور ۱۴۰۵)
|
||||
**مخاطب:** همکاران فروش، پشتیبانی و مدیران — با یا بدون تجربه CRM.
|
||||
|
||||
اسناد مرتبط: [`قابلیتهای-منتو.md`](قابلیتهای-منتو.md) · [`وضعیت-پیشرفت.md`](وضعیت-پیشرفت.md)
|
||||
اسناد مرتبط: [`قابلیتهای-منتکت.md`](قابلیتهای-منتکت.md) · [`وضعیت-پیشرفت.md`](وضعیت-پیشرفت.md)
|
||||
|
||||
---
|
||||
|
||||
## فهرست مطالب
|
||||
|
||||
1. [این راهنما چیست؟](#این-راهنما-چیست)
|
||||
2. [منتو چیست؟](#منتو-چیست-به-زبان-خیلی-ساده)
|
||||
2. [منتکت چیست؟](#منتکت-چیست-به-زبان-خیلی-ساده)
|
||||
3. [آدرسها و حسابهای دمو](#آدرسها-و-حسابهای-دمو)
|
||||
4. [شروع خیلی ساده](#شروع-خیلی-ساده-۱۰-دقیقه)
|
||||
5. [منوی کناری](#منوی-کناری-و-ساختار-صفحه)
|
||||
@@ -38,22 +38,22 @@
|
||||
|
||||
## این راهنما چیست؟
|
||||
|
||||
به زبان ساده میگوید منتو چیست، کجا بروید، چه دکمهای بزنید و نتیجه چه میشود.
|
||||
به زبان ساده میگوید منتکت چیست، کجا بروید، چه دکمهای بزنید و نتیجه چه میشود.
|
||||
اگر تازهکارید از **شروع خیلی ساده** شروع کنید.
|
||||
|
||||
---
|
||||
|
||||
## منتو چیست؟ (به زبان خیلی ساده)
|
||||
## منتکت چیست؟ (به زبان خیلی ساده)
|
||||
|
||||
منتو یک **CRM فارسی** است: دفترچه مشتری، تخته فروش، پیگیری، پشتیبانی، فاکتور، پرتال مشتری و (برای طلا و جواهر) اتصال به تهحساب — در یک جا.
|
||||
منتکت یک **CRM فارسی** است: دفترچه مشتری، تخته فروش، پیگیری، پشتیبانی، فاکتور، پرتال مشتری و (برای طلا و جواهر) اتصال به تهحساب — در یک جا.
|
||||
|
||||
| مفهوم | یعنی چه؟ |
|
||||
|---|---|
|
||||
| **سازمان** | شرکت شما داخل منتو |
|
||||
| **سازمان** | شرکت شما داخل منتکت |
|
||||
| **شخص / شرکت** | مخاطب حقیقی یا حقوقی |
|
||||
| **معامله** | یک فروش احتمالی روی کاریز |
|
||||
| **فعالیت** | تماس، جلسه، وظیفه |
|
||||
| **یادداشت** | ثبت سریع حرفها + هوش منتو |
|
||||
| **یادداشت** | ثبت سریع حرفها + هوش منتکت |
|
||||
| **تیکت** | درخواست پشتیبانی |
|
||||
| **کاریز پروژه** | کارهای داخلی پسازفروش |
|
||||
| **باشگاه** | امتیاز وفاداری مشتری |
|
||||
@@ -140,7 +140,7 @@
|
||||
|
||||
## ۲) خانه امروز
|
||||
|
||||
بعد از ورود میبینید: خلاصه فعالیتها، میانبرها، نمای کاریز/روند فروش، و امکان **پرسیدن از منتو** (Ask Mento) درباره کار امروز.
|
||||
بعد از ورود میبینید: خلاصه فعالیتها، میانبرها، نمای کاریز/روند فروش، و امکان **پرسیدن از منتکت** (Ask Montact) درباره کار امروز.
|
||||
|
||||
---
|
||||
|
||||
@@ -191,7 +191,7 @@
|
||||
2. نوع یادداشت را انتخاب کنید و متن را بنویسید (یا با دکمه **میکروفون** دیکته کنید).
|
||||
3. به شخص/شرکت/معامله وصل کنید؛ در صورت نیاز پین/برچسب/یادآور.
|
||||
4. پیشنهاد هوشمند یا Memory را فقط بعد از خواندن و **تأیید** اعمال کنید.
|
||||
5. از کادر **از منتو بپرس** سؤال زبان طبیعی بپرسید (مثلاً «آخرین بار درباره قیمت چه گفت؟»).
|
||||
5. از کادر **از منتکت بپرس** سؤال زبان طبیعی بپرسید (مثلاً «آخرین بار درباره قیمت چه گفت؟»).
|
||||
|
||||
پیوست فایل روی یادداشت از همان فرم قابل افزودن است.
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
# قابلیتهای منتو (Mento CRM)
|
||||
# قابلیتهای منتکت (Montact CRM)
|
||||
|
||||
فهرست کوتاه قابلیتهای محصول — هر تیتر یک امکان، با توضیح یکخطی.
|
||||
بهروزرسانی: ۱۴ شهریور ۱۴۰۵ · جزئیات کاربری: [`راهنمای-کاربری.md`](راهنمای-کاربری.md)
|
||||
@@ -33,7 +33,7 @@
|
||||
## خانه و پروفایل
|
||||
|
||||
### داشبورد خانه امروز
|
||||
خلاصه فروش، فعالیت، کاریز، میانبرها و دستیار Ask Mento در یک صفحه.
|
||||
خلاصه فروش، فعالیت، کاریز، میانبرها و دستیار Ask Montact در یک صفحه.
|
||||
|
||||
### پروفایل کاربر
|
||||
از کارت پایین سایدبار: وضعیت کارها و میانبرهای شخصی.
|
||||
@@ -126,10 +126,10 @@
|
||||
### پیشنهاد اقدام هوشمند
|
||||
AI پیشنهاد Next Action میدهد؛ اعمال فقط با تأیید کاربر.
|
||||
|
||||
### Mento Memory
|
||||
### Montact Memory
|
||||
خلاصه پایدار مشتری با منبع؛ قابل ویرایش و حذف.
|
||||
|
||||
### Ask Mento
|
||||
### Ask Montact
|
||||
پرسش به زبان طبیعی از روی یادداشتها و زمینه پرونده.
|
||||
|
||||
### دیکته صوتی (Voice-to-Text)
|
||||
@@ -212,7 +212,7 @@ AI پیشنهاد Next Action میدهد؛ اعمال فقط با تأیید
|
||||
ارزهای فعال و نرخ به ارز پایه.
|
||||
|
||||
### ظاهر یکدست و موبایلفرندلی
|
||||
زبان بصری منتو در لیستها، فرمها و تنظیمات؛ کار روی موبایل با منوی کشویی.
|
||||
زبان بصری منتکت در لیستها، فرمها و تنظیمات؛ کار روی موبایل با منوی کشویی.
|
||||
|
||||
---
|
||||
|
||||
@@ -235,7 +235,7 @@ AI پیشنهاد Next Action میدهد؛ اعمال فقط با تأیید
|
||||
## عمداً خارج از محصول فعلی
|
||||
|
||||
- اپ بومی اندروید/iOS جدا
|
||||
- حسابداری کامل و انبار ERP داخل خود منتو
|
||||
- حسابداری کامل و انبار ERP داخل خود منتکت
|
||||
- API عمومی مستند و وبهوک خروجی (در نقشه راه بعدی)
|
||||
- درگاه پرداخت آنلاین داخل پنل
|
||||
- لندینگ بازاریابی Nuxt (بعد از ثبات)
|
||||
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
# وضعیت پیشرفت منتو (Mento CRM)
|
||||
# وضعیت پیشرفت منتکت (Montact CRM)
|
||||
|
||||
سند زندهٔ کارها — با هر قابلیت جدید همین فایل بهروز میشود.
|
||||
آخرین بهروزرسانی: ۱۴ شهریور ۱۴۰۵
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
| تاریخ | مورد |
|
||||
|---|---|
|
||||
| ۱۴۰۵/۶/۱۴ | همتراز کردن ظاهر صفحات قدیمی با زبان بصری منتو (لیست/فرم/تنظیمات/گزارش) + جداکننده هزارگان روی فیلدهای مبلغ |
|
||||
| ۱۴۰۵/۶/۱۴ | همتراز کردن ظاهر صفحات قدیمی با زبان بصری منتکت (لیست/فرم/تنظیمات/گزارش) + جداکننده هزارگان روی فیلدهای مبلغ |
|
||||
| ۱۴۰۵/۶/۱۴ | یادآور فعالیت: کانال اعلان داخلبرنامه و/یا پیامک |
|
||||
| ۱۴۰۵/۶/۱۴ | انتخابگر زمان دایرهای در `JalaliDateInput` |
|
||||
| ۱۴۰۵/۶/۱۴ | اصلاح لایهبندی موبایل مخاطبین و یادداشتها |
|
||||
@@ -65,8 +65,8 @@
|
||||
| [x] | تایملاین یکپارچه | |
|
||||
| [x] | پیوست فایل روی Note | `uploads/` محلی؛ آماده MinIO |
|
||||
| [x] | AI: Next Action / پیشنهاد اقدام | با تأیید کاربر |
|
||||
| [x] | Mento Memory با منبع | خلاصه پایدار + ویرایش/حذف |
|
||||
| [x] | Ask Mento | پرسش زبان طبیعی روی یادداشتها / پروفایل / خانه |
|
||||
| [x] | Montact Memory با منبع | خلاصه پایدار + ویرایش/حذف |
|
||||
| [x] | Ask Montact | پرسش زبان طبیعی روی یادداشتها / پروفایل / خانه |
|
||||
| [x] | Voice-to-Text | دکمه دیکته (Pipecat/STT) روی Note و فرمها |
|
||||
| [ ] | Note شخصی در برابر تیمی | |
|
||||
| [ ] | تاریخچه ویرایش Note | |
|
||||
@@ -136,7 +136,7 @@
|
||||
|
||||
- نقشه راه: [`docs/راهنما-و-نقشه-راه.md`](راهنما-و-نقشه-راه.md)
|
||||
- راهنمای کاربری: [`docs/راهنمای-کاربری.md`](راهنمای-کاربری.md)
|
||||
- فهرست قابلیتها: [`docs/قابلیتهای-منتو.md`](قابلیتهای-منتو.md)
|
||||
- فهرست قابلیتها: [`docs/قابلیتهای-منتکت.md`](قابلیتهای-منتکت.md)
|
||||
- تهحساب: [`docs/integrations/tahesab.md`](integrations/tahesab.md)
|
||||
- استقرار: [`deploy/DEPLOY.md`](../deploy/DEPLOY.md)
|
||||
- خلاصه ریشه: [`README.md`](../README.md)
|
||||
|
||||
Binary file not shown.
+3
-2
@@ -2,15 +2,16 @@
|
||||
"name": "mento-crm",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"description": "Mento CRM (منتو) monorepo — Vue panel + NestJS API + PostgreSQL",
|
||||
"description": "Montact CRM (منتکت) monorepo — Vue panel + NestJS API + PostgreSQL",
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "npm run dev --workspaces --if-present",
|
||||
"dev": "npm run dev:web",
|
||||
"dev:web": "npm run dev -w @mento/web",
|
||||
"dev:api": "npm run start:dev -w @mento/api",
|
||||
"dev:all": "npm run dev --workspaces --if-present",
|
||||
"dev:stt": "apps/stt/.venv/Scripts/python.exe -m uvicorn main:app --host 127.0.0.1 --port 3040 --app-dir apps/stt",
|
||||
"build": "npm run build --workspaces --if-present",
|
||||
"build:shared": "npm run build -w @mento/shared",
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* تنها منبع نام و لوگوی محصول.
|
||||
* برای تغییر برند همین فایل را عوض کن؛ برای لوگو فایل
|
||||
* `apps/web/public/brand/logo.png` را جایگزین کن یا مسیر `logo` را اینجا تغییر بده.
|
||||
*/
|
||||
export const brand = {
|
||||
id: 'montact',
|
||||
name: 'Montact',
|
||||
nameFa: 'منتکت',
|
||||
fullName: 'Montact CRM',
|
||||
company: 'Mentech',
|
||||
taglineFa: 'نرمافزار مدیریت ارتباط با مشتری',
|
||||
logo: '/brand/logo.png',
|
||||
askPlaceholderFa: 'از منتکت بپرس...',
|
||||
askLabelFa: 'از منتکت بپرس',
|
||||
memoryEn: 'Montact Memory',
|
||||
aiName: 'Montact AI',
|
||||
aiNameFa: 'دستیار هوشمند منتکت',
|
||||
suggestionPrefixFa: 'پیشنهاد منتکت:',
|
||||
reminderPrefixFa: 'یادآوری منتکت:',
|
||||
filePrefix: 'montact',
|
||||
} as const;
|
||||
|
||||
export type Brand = typeof brand;
|
||||
|
||||
/** @deprecated از brand.fullName استفاده کنید */
|
||||
export const APP_NAME = brand.fullName;
|
||||
/** @deprecated از brand.nameFa استفاده کنید */
|
||||
export const APP_NAME_FA = brand.nameFa;
|
||||
|
||||
export function brandSuggestionFa(action: string) {
|
||||
return `${brand.suggestionPrefixFa} ${action}`;
|
||||
}
|
||||
@@ -1856,6 +1856,6 @@ export interface PersonAssetsDto {
|
||||
fetchedAt: string;
|
||||
}
|
||||
|
||||
export const APP_NAME = "Mento CRM";
|
||||
export const APP_NAME_FA = "منتو";
|
||||
export { brand, APP_NAME, APP_NAME_FA, brandSuggestionFa } from "./brand";
|
||||
export type { Brand } from "./brand";
|
||||
export const API_PREFIX = "api";
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Make near-black logo background transparent."""
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
FILES = [
|
||||
ROOT / "apps/web/public/brand/logo.png",
|
||||
ROOT / "apps/web/public/brand/montactLogo.png",
|
||||
]
|
||||
|
||||
|
||||
def knockout(src: Path) -> None:
|
||||
im = Image.open(src).convert("RGBA")
|
||||
pixels = im.load()
|
||||
w, h = im.size
|
||||
for y in range(h):
|
||||
for x in range(w):
|
||||
r, g, b, a = pixels[x, y]
|
||||
luma = 0.2126 * r + 0.7152 * g + 0.0722 * b
|
||||
chroma = max(r, g, b) - min(r, g, b)
|
||||
# Keep teal M (has chroma). Knock out near-black / gray canvas.
|
||||
if luma < 28 and chroma < 18:
|
||||
pixels[x, y] = (r, g, b, 0)
|
||||
elif luma < 48 and chroma < 22:
|
||||
pixels[x, y] = (r, g, b, int(a * (luma - 28) / 20))
|
||||
im.save(src)
|
||||
print(f"updated {src}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
for path in FILES:
|
||||
if path.exists():
|
||||
knockout(path)
|
||||
else:
|
||||
print(f"missing {path}")
|
||||
Reference in New Issue
Block a user