merge
This commit is contained in:
+2
-2
@@ -20,8 +20,8 @@ TELEGRAM_BOT_TOKEN = ''
|
||||
DOMAIN = 'heymlz.com'
|
||||
# domain for api (the domain that django will use)
|
||||
API_DOMAIN = 'api.heymlz.com'
|
||||
SITE_TITLE = ''
|
||||
SITE_HEADER = ''
|
||||
SITE_TITLE = 'Heymlz Shop'
|
||||
SITE_HEADER = 'Heymlz Shop'
|
||||
# jwt token configs
|
||||
ACCESS_TOKEN_LIFETIME = 5000
|
||||
REFRESH_TOKEN_LIFETIME = 5000
|
||||
|
||||
@@ -1,29 +1,39 @@
|
||||
from django.contrib import admin
|
||||
from .models import *
|
||||
from unfold.admin import ModelAdmin
|
||||
from unfold.admin import ModelAdmin, TabularInline
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from import_export.admin import ImportExportModelAdmin
|
||||
from unfold.contrib.import_export.forms import ExportForm, ImportForm, SelectableFieldsExportForm
|
||||
from unfold.contrib.forms.widgets import ArrayWidget, WysiwygWidget
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.contrib.auth.models import Group
|
||||
from unfold.forms import AdminPasswordChangeForm
|
||||
from unfold.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm
|
||||
|
||||
class UserAddressInLine(TabularInline):
|
||||
model = UserAddressModel
|
||||
extra = 0
|
||||
tab = True
|
||||
|
||||
@admin.register(User)
|
||||
class UserAdmin(BaseUserAdmin, ModelAdmin, ImportExportModelAdmin):
|
||||
|
||||
form = UserChangeForm
|
||||
add_form = UserCreationForm
|
||||
change_password_form = AdminPasswordChangeForm
|
||||
filter_horizontal = []
|
||||
ordering = []
|
||||
inlines = [UserAddressInLine]
|
||||
list_filter = []
|
||||
|
||||
search_fields = ['phone', 'first_name', 'last_name', ]
|
||||
list_display = ['phone', 'email', 'is_superuser']
|
||||
readonly_fields = []
|
||||
|
||||
exclude = ('otp_hash', 'otp_expiry', 'is_active', 'is_staff', 'password', 'last_login')
|
||||
import_form_class = ImportForm
|
||||
export_form_class = ExportForm
|
||||
|
||||
fieldsets = (
|
||||
('Personal info', {'fields': ('first_name', 'last_name', 'profile_photo')}),
|
||||
('contact', {'fields': ('phone', 'email')}),
|
||||
('Personal info', {'fields': ('first_name', 'last_name', 'profile_photo', 'password'),}),
|
||||
('contact', {'fields': ('phone', 'email'),}),
|
||||
)
|
||||
|
||||
add_fieldsets = (
|
||||
@@ -42,4 +52,30 @@ class UserAdmin(BaseUserAdmin, ModelAdmin, ImportExportModelAdmin):
|
||||
"widget": ArrayWidget,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
admin.site.unregister(Group)
|
||||
from django.contrib import admin
|
||||
from rest_framework_simplejwt.token_blacklist.models import BlacklistedToken, OutstandingToken
|
||||
# Unregister the BlacklistedToken and OutstandingToken models
|
||||
admin.site.unregister(BlacklistedToken)
|
||||
admin.site.unregister(OutstandingToken)
|
||||
|
||||
|
||||
@admin.register(UserAddressModel)
|
||||
class AddressAdmin(ModelAdmin, ImportExportModelAdmin):
|
||||
import_form_class = ImportForm
|
||||
export_form_class = ExportForm
|
||||
|
||||
|
||||
compressed_fields = True
|
||||
warn_unsaved_form = True
|
||||
|
||||
formfield_overrides = {
|
||||
models.TextField: {
|
||||
"widget": WysiwygWidget,
|
||||
},
|
||||
ArrayField: {
|
||||
"widget": ArrayWidget,
|
||||
}
|
||||
}
|
||||
@@ -4,3 +4,4 @@ from django.apps import AppConfig
|
||||
class AccountConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'account'
|
||||
verbose_name = 'اکانت'
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.1.2 on 2025-02-01 15:15
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='user',
|
||||
options={'verbose_name': 'کاربر', 'verbose_name_plural': 'کاربران'},
|
||||
),
|
||||
]
|
||||
@@ -1,4 +1,4 @@
|
||||
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
|
||||
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin, Group
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import random
|
||||
@@ -55,7 +55,9 @@ class User(AbstractBaseUser, PermissionsMixin):
|
||||
def user_permissions(self):
|
||||
return None
|
||||
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'کاربر'
|
||||
verbose_name_plural = 'کاربران'
|
||||
|
||||
def _hash_otp(self, otp):
|
||||
return hashlib.sha256(otp.encode()).hexdigest()
|
||||
|
||||
@@ -4,3 +4,4 @@ from django.apps import AppConfig
|
||||
class BlogConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'blog'
|
||||
verbose_name = 'بلاگ'
|
||||
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.1.2 on 2025-02-01 15:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('blog', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='blogmodel',
|
||||
options={'verbose_name': 'بلاگ', 'verbose_name_plural': 'بلاگ ها'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogmodel',
|
||||
name='cover_image',
|
||||
field=models.ImageField(blank=True, upload_to='blog_covers/'),
|
||||
),
|
||||
]
|
||||
@@ -26,6 +26,9 @@ class BlogModel(models.Model):
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'بلاگ'
|
||||
verbose_name_plural = 'بلاگ ها'
|
||||
|
||||
|
||||
# class Comment(models.Model):
|
||||
|
||||
@@ -1,13 +1,27 @@
|
||||
from rest_framework import serializers
|
||||
from .models import BlogModel
|
||||
from account.models import User
|
||||
|
||||
class AuthorSerializer(serializers.ModelSerializer):
|
||||
full_name = serializers.SerializerMethodField()
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['full_name', 'profile_photo']
|
||||
def get_full_name(self, obj):
|
||||
if obj.first_name and obj.last_name:
|
||||
return obj.first_name + ' ' + obj.last_name
|
||||
else:
|
||||
return 'ادمین وبسایت'
|
||||
|
||||
class BlogSerilizer(serializers.ModelSerializer):
|
||||
author = AuthorSerializer()
|
||||
class Meta:
|
||||
model = BlogModel
|
||||
fields = ['title','author', 'slug', 'category', 'created_at', 'updated_at', 'cover_image', 'views']
|
||||
exclude = ('is_published',)
|
||||
|
||||
|
||||
class AllBlogSerilizer(serializers.ModelSerializer):
|
||||
author = AuthorSerializer()
|
||||
class Meta:
|
||||
model = BlogModel
|
||||
exclude = ('is_published',)
|
||||
exclude = ('is_published', 'content', 'summery', )
|
||||
@@ -4,3 +4,4 @@ from django.apps import AppConfig
|
||||
class ChatConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'chat'
|
||||
verbose_name = 'چت هوش مصنوعی'
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.1.2 on 2025-02-01 15:15
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('chat', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='productchatmodel',
|
||||
options={'verbose_name': 'جت محصلول و کاربر', 'verbose_name_plural': 'چت های محصلول کاربر'},
|
||||
),
|
||||
]
|
||||
@@ -4,7 +4,7 @@ from product.models import ProductModel, DollorModel
|
||||
from django.conf import settings
|
||||
import openai
|
||||
from time import sleep
|
||||
from product.serializers import ProductChatSerializer
|
||||
from product.serializers import DynamicProductSerializer
|
||||
|
||||
|
||||
ASSISTANT_ID = 'asst_1wOnCKncEHkOfp0FjOIz4Xkp'
|
||||
@@ -15,6 +15,8 @@ class ProductChatModel(models.Model):
|
||||
|
||||
class Meta:
|
||||
unique_together = ['user', 'product']
|
||||
verbose_name = 'جت محصلول و کاربر'
|
||||
verbose_name_plural = 'چت های محصلول کاربر'
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.user} - {self.product}'
|
||||
@@ -24,7 +26,7 @@ class ProductChatModel(models.Model):
|
||||
client = openai.OpenAI(api_key=settings.OPENAI_API_KEY)
|
||||
dollor_object, _ = DollorModel.objects.get_or_create(unique_filed='unique')
|
||||
dollor_price = dollor_object.price
|
||||
product_json = ProductChatSerializer(instance=self.product, context={'dollor_price': dollor_price}).data
|
||||
product_json = DynamicProductSerializer(instance=self.product, context={'dollor_price': dollor_price, 'view_type': 'chat'}).data
|
||||
try:
|
||||
|
||||
thread = client.beta.threads.create(
|
||||
|
||||
+106
-45
@@ -178,6 +178,7 @@ STATIC_ROOT = '/app/static'
|
||||
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, 'custom_static'),
|
||||
# BASE_DIR / "core" / "static"
|
||||
]
|
||||
|
||||
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
||||
@@ -233,31 +234,50 @@ UNFOLD = {
|
||||
"href": lambda request: static("favicon.svg"),
|
||||
},
|
||||
],
|
||||
# "LOGIN": {
|
||||
# "image": lambda request: static("robot.png"),
|
||||
# },
|
||||
|
||||
|
||||
"BORDER_RADIUS": "15px",
|
||||
"SHOW_HISTORY": True,
|
||||
"SHOW_VIEW_ON_SITE": True,
|
||||
"ENVIRONMENT": "core.settings.environment_callback",
|
||||
|
||||
"COLORS": {
|
||||
"COLORS": {
|
||||
"base": {
|
||||
"50": "249 250 251",
|
||||
"100": "243 244 246",
|
||||
"200": "229 231 235",
|
||||
"300": "209 213 219",
|
||||
"400": "156 163 175",
|
||||
"500": "107 114 128",
|
||||
"600": "75 85 99",
|
||||
"700": "55 65 81",
|
||||
"800": "31 41 55",
|
||||
"900": "17 24 39",
|
||||
"950": "3 7 18"
|
||||
},
|
||||
"primary": {
|
||||
"50": "255 241 242",
|
||||
"100": "255 228 230",
|
||||
"200": "254 205 211",
|
||||
"300": "253 164 175",
|
||||
"400": "251 113 133",
|
||||
"500": "244 63 94",
|
||||
"600": "225 29 72",
|
||||
"700": "190 18 60",
|
||||
"800": "159 18 57",
|
||||
"900": "136 19 55",
|
||||
"950": "76 5 25"
|
||||
},
|
||||
"font": {
|
||||
"subtle-light": "107 114 128",
|
||||
"subtle-dark": "156 163 175",
|
||||
"default-light": "75 85 99",
|
||||
"default-dark": "209 213 219",
|
||||
"important-light": "17 24 39",
|
||||
"important-dark": "243 244 246",
|
||||
},
|
||||
"primary": {
|
||||
"50": "245 250 255",
|
||||
"100": "230 243 254",
|
||||
"200": "180 218 253",
|
||||
"300": "131 193 252",
|
||||
"400": "81 168 251",
|
||||
"500": "31 144 249",
|
||||
"600": "6 118 224",
|
||||
"700": "4 92 174",
|
||||
"800": "3 66 124",
|
||||
"900": "2 39 75",
|
||||
"950": "1 13 25"
|
||||
"subtle-light": "var(--color-base-500)", # text-base-500
|
||||
"subtle-dark": "var(--color-base-400)", # text-base-400
|
||||
"default-light": "var(--color-base-600)", # text-base-600
|
||||
"default-dark": "var(--color-base-300)", # text-base-300
|
||||
"important-light": "var(--color-base-900)", # text-base-900
|
||||
"important-dark": "var(--color-base-100)", # text-base-100
|
||||
},
|
||||
},
|
||||
"EXTENSIONS": {
|
||||
@@ -269,7 +289,7 @@ UNFOLD = {
|
||||
},
|
||||
|
||||
"SIDEBAR": {
|
||||
"show_search": True,
|
||||
"show_search": False,
|
||||
"show_all_applications": False,
|
||||
"navigation": [
|
||||
{
|
||||
@@ -283,9 +303,10 @@ UNFOLD = {
|
||||
"link": reverse_lazy("admin:index"),
|
||||
},
|
||||
{
|
||||
"title": _("اسلایدر"),
|
||||
"icon": "home",
|
||||
"link": reverse_lazy("admin:home_slidermodel_changelist"),
|
||||
"title": _("سفارشات"),
|
||||
"icon": "shopping_cart",
|
||||
"link": reverse_lazy("admin:order_ordermodel_changelist"),
|
||||
"badge": "utils.admin.admin_pending_count",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -293,9 +314,9 @@ UNFOLD = {
|
||||
|
||||
|
||||
{
|
||||
"title": _("پنل فروش محصولات وبسایت"),
|
||||
"title": _("Shop Products"),
|
||||
"separator": True,
|
||||
"collapsible": True,
|
||||
"collapsible": False,
|
||||
"items": [
|
||||
{
|
||||
"title": _("محصولات"),
|
||||
@@ -303,7 +324,27 @@ UNFOLD = {
|
||||
"link": reverse_lazy("admin:product_productmodel_changelist"),
|
||||
},
|
||||
|
||||
# esm category model ro lower case bezar inja amir
|
||||
{
|
||||
"title": _("نظرات"),
|
||||
"icon": "chat",
|
||||
"link": reverse_lazy("admin:product_commentmodel_changelist"),
|
||||
},
|
||||
{
|
||||
"title": _("قیمت دلار"),
|
||||
"icon": "payments",
|
||||
"link": reverse_lazy("admin:product_dollormodel_changelist"),
|
||||
"badge": "utils.admin.dollor_price",
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"title": _("Categories section"),
|
||||
"separator": True,
|
||||
"collapsible": False,
|
||||
"items": [
|
||||
|
||||
{
|
||||
"title": _("دسته بندی"),
|
||||
@@ -314,23 +355,38 @@ UNFOLD = {
|
||||
"title": _("زیر دسته بندی"),
|
||||
"icon": "category",
|
||||
"link": reverse_lazy("admin:product_subcategorymodel_changelist"),
|
||||
}
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
"title": _("Visual Sections "),
|
||||
"separator": True,
|
||||
"collapsible": True,
|
||||
"items": [
|
||||
|
||||
{
|
||||
"title": _("اسلاید ها"),
|
||||
"icon": "slide_library",
|
||||
"link": reverse_lazy("admin:home_slidermodel_changelist"),
|
||||
},
|
||||
{
|
||||
"title": _("نظرات"),
|
||||
"icon": "chat",
|
||||
"link": reverse_lazy("admin:product_commentmodel_changelist"),
|
||||
},
|
||||
"title": _("عکس مقایسه"),
|
||||
"icon": "compare",
|
||||
"link": reverse_lazy("admin:home_homeimagemodel_changelist"),
|
||||
}
|
||||
,
|
||||
{
|
||||
"title": _("قیمت دلار"),
|
||||
"icon": "payments",
|
||||
"link": reverse_lazy("admin:product_dollormodel_changelist"),
|
||||
},
|
||||
"title": _("مقالات و بلاگ ها"),
|
||||
"icon": "newsmode",
|
||||
"link": reverse_lazy("admin:blog_blogmodel_changelist"),
|
||||
}
|
||||
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
"title": _("بخش کاربران و مشتریان"),
|
||||
"title": _("Users and Customers"),
|
||||
"separator": True,
|
||||
"collapsible": True,
|
||||
"items": [
|
||||
@@ -339,30 +395,35 @@ UNFOLD = {
|
||||
"title": _("کاربران"),
|
||||
"icon": "person",
|
||||
"link": reverse_lazy("admin:account_user_changelist"),
|
||||
},{
|
||||
"title": _("چت محصول"),
|
||||
"icon": "chat",
|
||||
"link": reverse_lazy("admin:chat_productchatmodel_changelist"),
|
||||
},
|
||||
{
|
||||
"title": _("ادرس ها"),
|
||||
"icon": "contact_mail",
|
||||
"link": reverse_lazy("admin:account_useraddressmodel_changelist"),
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
"title": _("بخش هوش مصنوعی"),
|
||||
"title": _("Ticket and Support"),
|
||||
"separator": True,
|
||||
"collapsible": True,
|
||||
"items": [
|
||||
|
||||
{
|
||||
"title": _("چت محصول"),
|
||||
"icon": "chat",
|
||||
"link": reverse_lazy("admin:chat_productchatmodel_changelist"),
|
||||
"title": _("تیکت"),
|
||||
"icon": "confirmation_number",
|
||||
"link": reverse_lazy("admin:ticket_ticket_changelist"),
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
],
|
||||
},
|
||||
@@ -370,7 +431,7 @@ UNFOLD = {
|
||||
|
||||
AUTH_USER_MODEL = 'account.User'
|
||||
def environment_callback(request):
|
||||
return ["Development", "warning"]
|
||||
return ["Development", "danger"]
|
||||
|
||||
|
||||
def badge_callback(request):
|
||||
|
||||
@@ -4,3 +4,4 @@ from django.apps import AppConfig
|
||||
class HomeConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'home'
|
||||
verbose_name = 'خانه'
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.1.2 on 2025-02-01 15:15
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='homeimagemodel',
|
||||
options={'verbose_name': 'مدل عکس تفاوت خانه', 'verbose_name_plural': 'مدل عکس تفاوت خانه'},
|
||||
),
|
||||
]
|
||||
@@ -30,3 +30,6 @@ class HomeImageModel(models.Model):
|
||||
unique_filed = models.CharField(max_length=20, choices=unique, unique=True, default='unique')
|
||||
def __str__(self):
|
||||
return f'{self.title1} - {self.title2}'
|
||||
class Meta:
|
||||
verbose_name = 'مدل عکس تفاوت خانه'
|
||||
verbose_name_plural = 'مدل عکس تفاوت خانه'
|
||||
@@ -1,7 +1,7 @@
|
||||
from django.shortcuts import render
|
||||
from rest_framework.views import APIView, Response
|
||||
from product.models import ProductModel, SubCategoryModel, DollorModel
|
||||
from product.serializers import SubCategorySerializer, ProductSerializer
|
||||
from product.serializers import SubCategorySerializer, DynamicProductSerializer
|
||||
from .serializers import SliderSerializer, HomeImageSerializer
|
||||
from .models import SliderModel, HomeImageModel
|
||||
from rest_framework import status
|
||||
@@ -21,7 +21,7 @@ class HomeView(APIView):
|
||||
sub_category_ser = SubCategorySerializer(instance=sub_categories, many=True, context={'request': request})
|
||||
|
||||
products_to_show = ProductModel.objects.filter(show=True)
|
||||
product_ser = ProductSerializer(instance=products_to_show, many=True, context={'request': request, 'dollor_price': dollor_price})
|
||||
product_ser = DynamicProductSerializer(instance=products_to_show, many=True, context={'request': request, 'dollor_price': dollor_price, 'view_type': 'list'})
|
||||
|
||||
home_image = HomeImageModel.objects.all().first()
|
||||
home_image_ser = HomeImageSerializer(instance=home_image, context={'request': request})
|
||||
|
||||
@@ -4,3 +4,4 @@ from django.apps import AppConfig
|
||||
class OrderConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'order'
|
||||
verbose_name = 'سفارش'
|
||||
|
||||
@@ -1,23 +1,40 @@
|
||||
from django.contrib import admin
|
||||
from .models import *
|
||||
from unfold.admin import ModelAdmin
|
||||
from unfold.admin import ModelAdmin, TabularInline
|
||||
|
||||
from import_export.admin import ImportExportModelAdmin
|
||||
from unfold.contrib.import_export.forms import ExportForm, ImportForm, SelectableFieldsExportForm
|
||||
from unfold.contrib.forms.widgets import ArrayWidget, WysiwygWidget
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
|
||||
from unfold.widgets import (
|
||||
UnfoldAdminColorInputWidget,
|
||||
)
|
||||
from unfold.decorators import action, display
|
||||
class InStuckColorsInLine(TabularInline):
|
||||
model = InStuckColors
|
||||
extra = 0
|
||||
tab = True
|
||||
formfield_overrides = {
|
||||
models.CharField: {"widget": UnfoldAdminColorInputWidget()},
|
||||
}
|
||||
|
||||
@admin.register(ProductModel)
|
||||
class ProductModelAdmin(ModelAdmin, ImportExportModelAdmin):
|
||||
import_form_class = ImportForm
|
||||
export_form_class = ExportForm
|
||||
|
||||
inlines = [InStuckColorsInLine]
|
||||
readonly_fields = ('slug', )
|
||||
|
||||
compressed_fields = True
|
||||
search_fields = ['name']
|
||||
autocomplete_fields = ['related_products']
|
||||
# compressed_fields = True
|
||||
warn_unsaved_form = True
|
||||
list_display = ['display_image', 'price',]
|
||||
fieldsets = (
|
||||
('Main Fileds', {'fields': ('name', 'description', 'price', 'currency', 'discount', 'category', 'related_products', 'show',), "classes": ["tab"],}),
|
||||
('SEO Fileds', {'fields': ('meta_description', 'meta_keywords', 'meta_rating', 'slug'), "classes": ["tab"],}),
|
||||
('Users Fileds', {'fields': ('rating', 'view', 'sell', ), "classes": ["tab"],})
|
||||
|
||||
)
|
||||
formfield_overrides = {
|
||||
models.TextField: {
|
||||
"widget": WysiwygWidget,
|
||||
@@ -26,6 +43,32 @@ class ProductModelAdmin(ModelAdmin, ImportExportModelAdmin):
|
||||
"widget": ArrayWidget,
|
||||
}
|
||||
}
|
||||
@display(description='محصول', header=True)
|
||||
def display_image(self, instance):
|
||||
if instance.image1:
|
||||
return [
|
||||
instance.name,
|
||||
None,
|
||||
None,
|
||||
{
|
||||
"path": instance.image1.url,
|
||||
"height": 30,
|
||||
"width": 30,
|
||||
"borderless": True,
|
||||
# "squared": True,
|
||||
},
|
||||
]
|
||||
return ('خالی',)
|
||||
# @display(
|
||||
# description=("نمایش در صفحه ی اصلی"),
|
||||
# label={
|
||||
# True: "danger",
|
||||
# False: "success",
|
||||
# },
|
||||
# )
|
||||
# def display_show(self, instance):
|
||||
# return instance.show
|
||||
|
||||
|
||||
@admin.register(MainCategoryModel)
|
||||
class MainCategoryModelAdmin(ModelAdmin, ImportExportModelAdmin):
|
||||
|
||||
@@ -4,3 +4,4 @@ from django.apps import AppConfig
|
||||
class ProductConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'product'
|
||||
verbose_name = 'محصول'
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
# Generated by Django 5.1.2 on 2025-02-01 15:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('product', '0004_alter_subcategorymodel_parent'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='dollormodel',
|
||||
options={'verbose_name': 'مدل دلار', 'verbose_name_plural': 'مدل دلار'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='productmodel',
|
||||
options={'verbose_name': 'محصول', 'verbose_name_plural': 'محصولات'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='productmodel',
|
||||
name='color',
|
||||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='color'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 5.1.2 on 2025-02-01 15:32
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('product', '0005_alter_dollormodel_options_alter_productmodel_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='InStuckColors',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('color', models.CharField(blank=True, max_length=255, null=True, verbose_name='color')),
|
||||
('in_stuck', models.PositiveIntegerField(default=0)),
|
||||
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='colors', to='product.productmodel')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'تعداد موجود رنگ',
|
||||
'verbose_name_plural': 'تعداد موجود رنگ ها',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.1.2 on 2025-02-01 15:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('product', '0006_instuckcolors'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='productmodel',
|
||||
name='in_stock',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='instuckcolors',
|
||||
name='in_stuck',
|
||||
field=models.PositiveIntegerField(default=0, verbose_name='تعداد موجود'),
|
||||
),
|
||||
]
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.1.2 on 2025-02-01 15:54
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('product', '0007_remove_productmodel_in_stock_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='productmodel',
|
||||
name='color',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='instuckcolors',
|
||||
name='color',
|
||||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='رنگ'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.2 on 2025-02-01 16:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('product', '0008_remove_productmodel_color_alter_instuckcolors_color'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='productmodel',
|
||||
name='related_products',
|
||||
field=models.ManyToManyField(to='product.productmodel'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.2 on 2025-02-01 18:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('product', '0009_productmodel_related_products'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='productmodel',
|
||||
name='related_products',
|
||||
field=models.ManyToManyField(blank=True, null=True, to='product.productmodel'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.1.2 on 2025-02-01 23:47
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('product', '0010_alter_productmodel_related_products'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='productmodel',
|
||||
name='category',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='products', to='product.subcategorymodel', verbose_name='دسته بندی محصول'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='productmodel',
|
||||
name='related_products',
|
||||
field=models.ManyToManyField(blank=True, to='product.productmodel'),
|
||||
),
|
||||
]
|
||||
@@ -3,7 +3,7 @@ from django.utils.text import slugify
|
||||
from account.models import User
|
||||
from django.urls import reverse
|
||||
import requests
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
class MainCategoryModel(models.Model):
|
||||
name = models.CharField(max_length=50, verbose_name='نام')
|
||||
@@ -80,6 +80,9 @@ class DollorModel(models.Model):
|
||||
return self.defualt_price
|
||||
return price_in_usd
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'مدل دلار'
|
||||
verbose_name_plural = 'مدل دلار'
|
||||
|
||||
|
||||
class ProductModel(models.Model):
|
||||
@@ -100,7 +103,6 @@ class ProductModel(models.Model):
|
||||
show = models.BooleanField(default=False, verbose_name='نمایش در خانه')
|
||||
view = models.IntegerField(default=0, verbose_name='بازدید')
|
||||
sell = models.IntegerField(default=0, verbose_name='فروش')
|
||||
in_stock = models.IntegerField(default=0, verbose_name="تعداد موجود")
|
||||
discount = models.SmallIntegerField(default=0, verbose_name='تخفیف')
|
||||
slug = models.SlugField(max_length=50, unique=True, blank=True, null=True, allow_unicode=True,
|
||||
verbose_name='نام یکتا', help_text="این فیلد را خالی بگذارید")
|
||||
@@ -108,7 +110,8 @@ class ProductModel(models.Model):
|
||||
meta_keywords = models.CharField(max_length=300, blank=True, null=True, help_text='این فیلد را حتما پر کنید')
|
||||
meta_rating = models.FloatField(default=5, help_text='امتیاز محصول')
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='زمان ثبت محصول')
|
||||
category = models.ForeignKey(SubCategoryModel, blank=True, null=True, on_delete=models.SET_NULL, related_name='products', verbose_name='دسته بندی محصول')
|
||||
category = models.ForeignKey(SubCategoryModel, null=True, on_delete=models.SET_NULL, related_name='products', verbose_name='دسته بندی محصول')
|
||||
related_products = models.ManyToManyField('self', blank=True,)
|
||||
def format_discount_price(self):
|
||||
discount_price = int(self.price * (100 - self.discount) / 100)
|
||||
formatted_num = "{:,.0f}".format(discount_price)
|
||||
@@ -140,6 +143,20 @@ class ProductModel(models.Model):
|
||||
self.slug = slugify(self.name, allow_unicode=True)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'محصول'
|
||||
verbose_name_plural = 'محصولات'
|
||||
|
||||
|
||||
class InStuckColors(models.Model):
|
||||
color = models.CharField(_("رنگ"), null=True, blank=True, max_length=255)
|
||||
in_stuck = models.PositiveIntegerField(default=0, verbose_name="تعداد موجود")
|
||||
product = models.ForeignKey(ProductModel, on_delete=models.CASCADE, related_name='colors')
|
||||
class Meta:
|
||||
verbose_name = 'تعداد موجود رنگ'
|
||||
verbose_name_plural = 'تعداد موجود رنگ ها'
|
||||
def __str__(self):
|
||||
return f'{self.product} - {self.color}'
|
||||
|
||||
class CommentModel(models.Model):
|
||||
product = models.ForeignKey(ProductModel, on_delete=models.CASCADE, related_name='comments', verbose_name='محصول')
|
||||
|
||||
@@ -3,12 +3,39 @@ from rest_framework import serializers
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
class ProductChatSerializer(serializers.ModelSerializer):
|
||||
|
||||
class InStuckColorsSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = InStuckColors
|
||||
fields = ['color', 'in_stuck']
|
||||
|
||||
|
||||
class DynamicProductSerializer(serializers.ModelSerializer):
|
||||
colors = InStuckColorsSerializer(many=True, read_only=True)
|
||||
price = serializers.SerializerMethodField()
|
||||
is_new = serializers.SerializerMethodField()
|
||||
related_products = serializers.SerializerMethodField()
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
view_type = self.context.get('view_type', 'all')
|
||||
if view_type != 'all':
|
||||
allowed_fields = self.Meta.view_type[view_type]
|
||||
allowed = set(allowed_fields)
|
||||
existing = set(self.fields.keys())
|
||||
|
||||
for field_name in existing - allowed:
|
||||
self.fields.pop(field_name)
|
||||
|
||||
|
||||
class Meta:
|
||||
model = ProductModel
|
||||
fields = ['name', 'description', 'price', 'in_stock', 'discount', 'is_new']
|
||||
fields = "__all__"
|
||||
view_type = {
|
||||
'list': ['name', 'price', 'image1', 'video', 'rating', 'discount', 'slug', 'category', 'colors'],
|
||||
'instance': ['name', 'description', 'price', 'image1', 'image2', 'image3', 'video', 'rating', 'discount', 'slug', 'meta_description', 'meta_keywords', 'meta_rating', 'category', 'colors', 'related_products'],
|
||||
'chat': ['name', 'description', 'price', 'in_stock', 'discount', 'colors']
|
||||
}
|
||||
|
||||
def get_price(self, obj):
|
||||
dollor_price = self.context.get('dollor_price')
|
||||
dollar_to_dirham = 0.27
|
||||
@@ -21,13 +48,25 @@ class ProductChatSerializer(serializers.ModelSerializer):
|
||||
elif obj.currency == 'derham':
|
||||
toman_price = obj.price * dollor_price * dollar_to_dirham
|
||||
return "{:,.0f} تومان".format(toman_price)
|
||||
|
||||
def get_is_new(self, obj):
|
||||
return timezone.now() < obj.created_at + timedelta(days=7)
|
||||
|
||||
class ProductSerializer(ProductChatSerializer):
|
||||
class Meta:
|
||||
model = ProductModel
|
||||
fields = "__all__"
|
||||
def get_related_products(self, obj):
|
||||
if obj.related_products.all().count() >= 5:
|
||||
related_products = obj.related_products.all()
|
||||
else:
|
||||
related_products = obj.category.products
|
||||
serializer = DynamicProductSerializer(
|
||||
related_products,
|
||||
many=True,
|
||||
context={
|
||||
'view_type': 'list',
|
||||
'dollor_price': self.context.get('dollor_price')
|
||||
}
|
||||
)
|
||||
return serializer.data
|
||||
|
||||
|
||||
class CommentSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
|
||||
@@ -49,19 +49,19 @@ class AllCategories(APIView):
|
||||
return Response(categories_ser.data, status=status.HTTP_200_OK)
|
||||
|
||||
class ProductView(APIView):
|
||||
serializer_class = ProductSerializer
|
||||
serializer_class = DynamicProductSerializer
|
||||
permission_classes = [AllowAny]
|
||||
authentication_classes = []
|
||||
def get(self, request, pk):
|
||||
product = get_object_or_404(ProductModel, id=pk)
|
||||
dollor_object, _ = DollorModel.objects.get_or_create(unique_filed='unique')
|
||||
dollor_price = dollor_object.price
|
||||
product_ser = self.serializer_class(instance=product, many=False, context={'dollor_price': dollor_price, 'request': request})
|
||||
product_ser = self.serializer_class(instance=product, many=False, context={'dollor_price': dollor_price, 'request': request, 'view_type': 'instance'})
|
||||
return Response(product_ser.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class AllProductsView(APIView):
|
||||
serializer_class = ProductSerializer
|
||||
serializer_class = DynamicProductSerializer
|
||||
pagination_class = StructurePagination
|
||||
authentication_classes = []
|
||||
@extend_schema(
|
||||
@@ -137,7 +137,7 @@ class AllProductsView(APIView):
|
||||
"Provide a list of category IDs to filter products by those categories and their subcategories."
|
||||
),
|
||||
responses={
|
||||
200: ProductSerializer(many=True),
|
||||
200: DynamicProductSerializer(many=True, context={'view_type': 'list'}),
|
||||
404: OpenApiTypes.OBJECT,
|
||||
},
|
||||
)
|
||||
@@ -191,7 +191,7 @@ class AllProductsView(APIView):
|
||||
paginated_products = paginator.paginate_queryset(products, request)
|
||||
dollor_object, _ = DollorModel.objects.get_or_create(unique_filed='unique')
|
||||
dollor_price = dollor_object.price
|
||||
serializer = self.serializer_class(paginated_products, many=True, context={'dollor_price': dollor_price, 'request': request})
|
||||
serializer = self.serializer_class(paginated_products, many=True, context={'dollor_price': dollor_price, 'request': request, 'view_type': 'list'})
|
||||
return paginator.get_paginated_response(serializer.data)
|
||||
|
||||
except MainCategoryModel.DoesNotExist:
|
||||
|
||||
@@ -19,7 +19,7 @@ django-dbbackup==4.2.1
|
||||
django-filter==24.3
|
||||
django-import-export==4.1.1
|
||||
django-iranian-cities==1.0.2
|
||||
django-unfold==0.39.0
|
||||
django-unfold==0.46.0
|
||||
djangorestframework==3.15.2
|
||||
djangorestframework-simplejwt==5.3.1
|
||||
djoser==2.3.1
|
||||
|
||||
@@ -26,6 +26,7 @@ class TicketAdmin(ModelAdmin, ImportExportModelAdmin):
|
||||
}
|
||||
}
|
||||
inlines = [MessageInline]
|
||||
radio_fields = {'status': admin.VERTICAL}
|
||||
|
||||
@admin.register(Message)
|
||||
class MessageAdmin(ModelAdmin, ImportExportModelAdmin):
|
||||
|
||||
@@ -4,3 +4,4 @@ from django.apps import AppConfig
|
||||
class TicketConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'ticket'
|
||||
verbose_name = 'تیکت'
|
||||
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 5.1.2 on 2025-02-01 15:15
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ticket', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='message',
|
||||
options={'verbose_name': 'پیام تیکت', 'verbose_name_plural': 'پیام های تیکت'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='ticket',
|
||||
options={'verbose_name': 'تیکت', 'verbose_name_plural': 'تیکت ها'},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.2 on 2025-02-01 23:47
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ticket', '0002_alter_message_options_alter_ticket_options'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ticket',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('open', 'باز'), ('in_progress', 'در حال پردازش'), ('resolved', 'حل شده'), ('closed', 'بسته')], default='open', max_length=20),
|
||||
),
|
||||
]
|
||||
@@ -3,10 +3,10 @@ from account.models import User
|
||||
|
||||
class Ticket(models.Model):
|
||||
STATUS_CHOICES = [
|
||||
('open', 'یاز'),
|
||||
('open', 'باز'),
|
||||
('in_progress', 'در حال پردازش'),
|
||||
('resolved', 'حل شده'),
|
||||
('closed', 'باز'),
|
||||
('closed', 'بسته'),
|
||||
]
|
||||
|
||||
subject = models.CharField(max_length=255)
|
||||
@@ -19,6 +19,12 @@ class Ticket(models.Model):
|
||||
def __str__(self):
|
||||
return self.subject
|
||||
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'تیکت'
|
||||
verbose_name_plural = 'تیکت ها'
|
||||
|
||||
|
||||
class Message(models.Model):
|
||||
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, related_name="messages")
|
||||
sender = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
@@ -26,4 +32,8 @@ class Message(models.Model):
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Message by {self.sender.username} on {self.ticket.subject}"
|
||||
return f"Message by {self.sender.username} on {self.ticket.subject}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'پیام تیکت'
|
||||
verbose_name_plural = 'پیام های تیکت'
|
||||
@@ -0,0 +1,9 @@
|
||||
from order.models import OrderModel
|
||||
from product.models import DollorModel
|
||||
def admin_pending_count(request):
|
||||
pending_count = OrderModel.objects.filter(status='ADMIN_PENDING').count()
|
||||
return str(pending_count)
|
||||
|
||||
def dollor_price(request):
|
||||
dollor_object, _ = DollorModel.objects.get_or_create(unique_filed='unique')
|
||||
return str(dollor_object.price)[:2]
|
||||
@@ -0,0 +1,14 @@
|
||||
# from unfold.widgets import UnfoldAdminCheckboxSelectMultiple
|
||||
# from django import forms
|
||||
# class DriverAdminForm(forms.ModelForm):
|
||||
# flags = forms.MultipleChoiceField(
|
||||
# label=("Flags"),
|
||||
# choices=[
|
||||
# ("POPULAR", ("Popular")),
|
||||
# ("FASTEST", ("Fastest")),
|
||||
# ("TALENTED", ("Talented")),
|
||||
# ],
|
||||
# required=False,
|
||||
# widget=UnfoldAdminCheckboxSelectMultiple,
|
||||
# )
|
||||
# form = DriverAdminForm
|
||||
@@ -25,7 +25,7 @@ const {} = toRefs(props);
|
||||
<template>
|
||||
<div
|
||||
:class="variant === 'lg' ? 'rounded-150 overflow-hidden' : ''"
|
||||
class="max-h-[700px] h-[700px] relative"
|
||||
class="group max-h-[700px] h-[700px] relative"
|
||||
>
|
||||
|
||||
<Tag
|
||||
@@ -47,7 +47,7 @@ const {} = toRefs(props);
|
||||
|
||||
<img
|
||||
:src="image"
|
||||
class="absolute size-full object-cover z-10"
|
||||
class="group-hover:scale-105 transition-transform duration-200 absolute size-full object-cover z-10"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
@@ -106,7 +106,7 @@ const {} = toRefs(props);
|
||||
<img
|
||||
v-if="variant === 'lg'"
|
||||
:src="image"
|
||||
class="absolute size-full object-cover z-10"
|
||||
class="group-hover:scale-105 transition-transform duration-200 absolute size-full object-cover z-10"
|
||||
alt=""
|
||||
/>
|
||||
|
||||
|
||||
@@ -38,23 +38,23 @@ watch(
|
||||
show-edges
|
||||
v-model:page="page"
|
||||
>
|
||||
<PaginationList v-slot="{ items }" class="flex items-center gap-1">
|
||||
<PaginationList v-slot="{ items }" class="flex items-center gap-2">
|
||||
<PaginationLast
|
||||
class="w-9 h-9 flex items-center justify-center bg-transparent cursor-pointer hover:bg-stone-700/20 transition disabled:opacity-50 disabled:cursor-not-allowed rounded-lg"
|
||||
class="px-2 h-9 font-light whitespace-nowrap flex items-center justify-center bg-transparent cursor-pointer transition disabled:opacity-50 disabled:cursor-not-allowed rounded-lg"
|
||||
>
|
||||
<Icon name="bi:chevron-double-right" class="**:stroke-black" />
|
||||
برو آخر
|
||||
</PaginationLast>
|
||||
<PaginationNext
|
||||
class="w-9 h-9 ml-4 flex items-center justify-center cursor-pointer hover:bg-stone-700/20 transition disabled:opacity-50 disabled:cursor-not-allowed rounded-lg"
|
||||
class="w-9 h-9 ml-4 flex items-center justify-center cursor-pointer hover:bg-slate-100 transition disabled:opacity-50 disabled:cursor-not-allowed rounded-lg"
|
||||
>
|
||||
<Icon name="bi:chevron-right" class="**:stroke-black" />
|
||||
<Icon name="ci:chevron-right" class="**:fill-back" size="18px" />
|
||||
</PaginationNext>
|
||||
|
||||
<template v-for="(page, index) in items">
|
||||
<PaginationListItem
|
||||
v-if="page.type === 'page'"
|
||||
:key="index"
|
||||
class="w-9 h-9 border border-stone-800 rounded-lg data-[selected]:!bg-black data-[selected]:text-white data-[selected]:shadow-sm hover:bg-stone-700/20 transition"
|
||||
class="w-9 h-9 cursor-pointer bg-slate-100 rounded-lg data-[selected]:!bg-black data-[selected]:text-white data-[selected]:shadow-sm hover:bg-slate-200 transition"
|
||||
:value="page.value"
|
||||
>
|
||||
{{ page.value }}
|
||||
@@ -63,21 +63,21 @@ watch(
|
||||
v-else
|
||||
:key="page.type"
|
||||
:index="index"
|
||||
class="w-9 h-9 flex items-center justify-center"
|
||||
class="w-9 h-9 select-none flex items-center justify-center"
|
||||
>
|
||||
…
|
||||
</PaginationEllipsis>
|
||||
</template>
|
||||
|
||||
<PaginationPrev
|
||||
class="w-9 h-9 flex items-center justify-center bg-transparent cursor-pointer hover:bg-stone-700/20 transition mr-4 disabled:opacity-50 disabled:cursor-not-allowed rounded-lg"
|
||||
class="w-9 h-9 flex items-center justify-center bg-transparent cursor-pointer hover:bg-slate-100 transition mr-4 disabled:opacity-50 disabled:cursor-not-allowed rounded-lg"
|
||||
>
|
||||
<Icon name="bi:chevron-left" class="**:stroke-black" />
|
||||
<Icon name="ci:chevron-left" class="**:fill-back" size="18px" />
|
||||
</PaginationPrev>
|
||||
<PaginationFirst
|
||||
class="w-9 h-9 flex items-center justify-center bg-transparent cursor-pointer hover:bg-stone-700/20 transition disabled:opacity-50 disabled:cursor-not-allowed rounded-lg"
|
||||
class="px-2 h-9 font-light flex items-center whitespace-nowrap justify-center bg-transparent cursor-pointer transition disabled:opacity-50 disabled:cursor-not-allowed rounded-lg"
|
||||
>
|
||||
<Icon name="bi:chevron-double-left" class="**:stroke-black" />
|
||||
برو اول
|
||||
</PaginationFirst>
|
||||
</PaginationList>
|
||||
</PaginationRoot>
|
||||
|
||||
@@ -17,9 +17,11 @@ const {} = toRefs(props);
|
||||
<span class="typo-h-4 text-black">
|
||||
مقالات اخیر سایت
|
||||
</span>
|
||||
<Button variant="outlined" class="rounded-full" start-icon="ci:paper">
|
||||
نمایش همه
|
||||
</Button>
|
||||
<NuxtLink to="/articles">
|
||||
<Button variant="outlined" class="rounded-full" start-icon="ci:paper">
|
||||
نمایش همه
|
||||
</Button>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div class="flex gap-12">
|
||||
<div class="flex-1 flex flex-col gap-12">
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// imports
|
||||
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { API_ENDPOINTS, QUERY_KEYS } from "~/constants";
|
||||
|
||||
// types
|
||||
|
||||
export type GetArticlesResponse = ApiPaginated<UserComment>;
|
||||
|
||||
const useGetArticles = (
|
||||
page: Ref<number>,
|
||||
search: Ref<string>
|
||||
) => {
|
||||
|
||||
// state
|
||||
|
||||
const { $axios: axios } = useNuxtApp();
|
||||
|
||||
// methods
|
||||
|
||||
const handleGetArticles = async () => {
|
||||
const { data } = await axios.get<GetArticlesResponse>(`${API_ENDPOINTS.blog.articles}`, {
|
||||
params: {
|
||||
offset: (page.value * 10) - 10,
|
||||
limit: 10,
|
||||
search: search.value.length > 0 ? search.value : undefined,
|
||||
}
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
return useQuery({
|
||||
queryKey: [QUERY_KEYS.articles, page, search],
|
||||
queryFn: () => handleGetArticles()
|
||||
});
|
||||
};
|
||||
|
||||
export default useGetArticles;
|
||||
@@ -1,5 +1,9 @@
|
||||
export const API_ENDPOINTS = {
|
||||
home : "/home",
|
||||
home: "/home",
|
||||
blog: {
|
||||
articles: "/blogs/all",
|
||||
article: "/blogs"
|
||||
},
|
||||
account: {
|
||||
profile: "/accounts/profile",
|
||||
send_otp: "/accounts/send_otp"
|
||||
@@ -26,6 +30,8 @@ export const API_ENDPOINTS = {
|
||||
};
|
||||
|
||||
export const QUERY_KEYS = {
|
||||
articles: "articles",
|
||||
article: "article",
|
||||
comments: "comments",
|
||||
home: "home",
|
||||
chat: "chat",
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
// import
|
||||
|
||||
import useGetArticles from "~/composables/api/blog/useGetArticles";
|
||||
|
||||
// state
|
||||
|
||||
const page = ref(1);
|
||||
const search = ref("");
|
||||
const debouncedSearch = refDebounced(search, 700);
|
||||
|
||||
const { data: articles, suspense } = useGetArticles(page, debouncedSearch);
|
||||
|
||||
// ssr
|
||||
|
||||
await useAsyncData(async () => {
|
||||
|
||||
const response = await suspense();
|
||||
|
||||
if (response.isError) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: `Error in categories page prefetch`
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container" style="margin-top: 80px">
|
||||
<div class="flex items-center justify-between mb-20">
|
||||
<span class="typo-h-4 text-black">
|
||||
مقالات اخیر سایت
|
||||
</span>
|
||||
<Input
|
||||
class="max-w-[400px] w-full rounded-full"
|
||||
variant="outlined"
|
||||
placeholder="جستجو..."
|
||||
v-model="search"
|
||||
>
|
||||
<template #endItem>
|
||||
<div class="flex items-center gap-1">
|
||||
<Icon
|
||||
class="translate-y-[-1px]"
|
||||
name="ci:search"
|
||||
size="24"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Input>
|
||||
</div>
|
||||
<div class="flex gap-12">
|
||||
<div class="flex-1 flex flex-col gap-12">
|
||||
<BlogPost
|
||||
image="/img/blog-1.jpeg"
|
||||
description="aaasd"
|
||||
title="asd"
|
||||
:comments="2"
|
||||
link="#"
|
||||
date="2020-06-10"
|
||||
tag="asdsa"
|
||||
/>
|
||||
<BlogPost
|
||||
image="/img/blog-2.jpeg"
|
||||
description="aaasd"
|
||||
title="asd"
|
||||
:comments="2"
|
||||
link="#"
|
||||
date="2020-06-10"
|
||||
tag="asdsa"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-[0.8] flex flex-col">
|
||||
<BlogPost
|
||||
image="/img/blog-3.jpeg"
|
||||
description="aaasd"
|
||||
title="asd"
|
||||
:comments="2"
|
||||
link="#"
|
||||
date="2020-06-10"
|
||||
tag="asdsa"
|
||||
variant="sm"
|
||||
/>
|
||||
<BlogPost
|
||||
image="/img/blog-4.jpeg"
|
||||
description="aaasd"
|
||||
title="asd"
|
||||
:comments="2"
|
||||
link="#"
|
||||
date="2020-06-10"
|
||||
tag="asdsa"
|
||||
variant="sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full flex-center pt-24 pb-10">
|
||||
<Pagination :items="[]" :total="100" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user