From 1240c8dfb47502199be4e5f78856dcfc70eb945b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Thu, 21 Aug 2025 14:41:59 +0300 Subject: [PATCH] =?UTF-8?q?About=20ve=20Contact=20sayfalar=C4=B1=20dinamik?= =?UTF-8?q?=20=C5=9Fekilde=20de=C4=9Fi=C5=9Ftirildi.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../About/AboutDto.cs | 68 +++ .../Contact/ContactDto.cs | 81 +++ .../Public/PublicAppService.cs | 23 +- .../Public/PublicAutoMapperProfile.cs | 4 + .../Seeds/PlatformDataSeeder.cs | 501 ++++++++++-------- .../Seeds/SeederData.json | 83 +++ .../Seeds/SeederDto.cs | 23 + .../Kurs.Platform.Domain/Entities/About.cs | 28 + .../Kurs.Platform.Domain/Entities/Contact.cs | 45 ++ .../EntityFrameworkCore/PlatformDbContext.cs | 31 ++ ...20250821081019_GenelDuzenleme.Designer.cs} | 128 ++++- ...me.cs => 20250821081019_GenelDuzenleme.cs} | 55 ++ .../PlatformDbContextModelSnapshot.cs | 126 +++++ ui/dev-dist/sw.js | 2 +- ui/src/proxy/about/models.ts | 22 + ui/src/proxy/contact/models.ts | 34 ++ ui/src/services/about.ts | 12 + ui/src/services/contact.ts | 12 + ui/src/utils/hooks/useCountUp.ts | 62 --- ui/src/views/public/About.tsx | 179 ++----- ui/src/views/public/Contact.tsx | 136 ++--- ui/src/views/public/Services.tsx | 127 +---- 22 files changed, 1177 insertions(+), 605 deletions(-) create mode 100644 api/src/Kurs.Platform.Application.Contracts/About/AboutDto.cs create mode 100644 api/src/Kurs.Platform.Application.Contracts/Contact/ContactDto.cs create mode 100644 api/src/Kurs.Platform.Domain/Entities/About.cs create mode 100644 api/src/Kurs.Platform.Domain/Entities/Contact.cs rename api/src/Kurs.Platform.EntityFrameworkCore/Migrations/{20250820190627_GenelDuzenleme.Designer.cs => 20250821081019_GenelDuzenleme.Designer.cs} (98%) rename api/src/Kurs.Platform.EntityFrameworkCore/Migrations/{20250820190627_GenelDuzenleme.cs => 20250821081019_GenelDuzenleme.cs} (91%) create mode 100644 ui/src/proxy/about/models.ts create mode 100644 ui/src/proxy/contact/models.ts create mode 100644 ui/src/services/about.ts create mode 100644 ui/src/services/contact.ts delete mode 100644 ui/src/utils/hooks/useCountUp.ts diff --git a/api/src/Kurs.Platform.Application.Contracts/About/AboutDto.cs b/api/src/Kurs.Platform.Application.Contracts/About/AboutDto.cs new file mode 100644 index 00000000..5987b5aa --- /dev/null +++ b/api/src/Kurs.Platform.Application.Contracts/About/AboutDto.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using Volo.Abp.Application.Dtos; + +namespace Kurs.Platform.Abouts; + +public class AboutDto : EntityDto +{ + public Guid? TenantId { get; set; } + + [JsonIgnore] + public string StatsJson { get; set; } + public List StatsDto + { + get + { + if (!string.IsNullOrEmpty(StatsJson)) + return JsonSerializer.Deserialize>(StatsJson); + return new List(); + } + set { StatsJson = JsonSerializer.Serialize(value); } + } + + [JsonIgnore] + public string DescriptionsJson { get; set; } + public List DescriptionsDto + { + get + { + if (!string.IsNullOrEmpty(DescriptionsJson)) + return JsonSerializer.Deserialize>(DescriptionsJson); + return []; + } + set { DescriptionsJson = JsonSerializer.Serialize(value); } + } + + [JsonIgnore] + public string SectionsJson { get; set; } + public List SectionsDto + { + get + { + if (!string.IsNullOrEmpty(SectionsJson)) + return JsonSerializer.Deserialize>(SectionsJson); + return new List(); + } + set { SectionsJson = JsonSerializer.Serialize(value); } + } + + public class StatDto + { + public string Icon { get; set; } + public string Value { get; set; } // number/string farkını normalize ettik + public string LabelKey { get; set; } + public bool? UseCounter { get; set; } + public int? CounterEnd { get; set; } + public string CounterSuffix { get; set; } + public int? CounterDuration { get; set; } + } + + public class SectionDto + { + public string Key { get; set; } + public string DescKey { get; set; } + } +} diff --git a/api/src/Kurs.Platform.Application.Contracts/Contact/ContactDto.cs b/api/src/Kurs.Platform.Application.Contracts/Contact/ContactDto.cs new file mode 100644 index 00000000..e8062aa6 --- /dev/null +++ b/api/src/Kurs.Platform.Application.Contracts/Contact/ContactDto.cs @@ -0,0 +1,81 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using Volo.Abp.Application.Dtos; + +namespace Kurs.Platform.Contacts; + +public class ContactDto : EntityDto +{ + public string Address { get; set; } + public string Phone { get; set; } + public string Email { get; set; } + public string Location { get; set; } + public string TaxNumber { get; set; } + + [JsonIgnore] + public string BankJson { get; set; } + public BankDto BankDto + { + get + { + if (!string.IsNullOrEmpty(BankJson)) + return JsonSerializer.Deserialize(BankJson); + return new BankDto(); + } + set { BankJson = JsonSerializer.Serialize(value); } + } + + [JsonIgnore] + public string WorkHoursJson { get; set; } + public WorkHoursDto WorkHoursDto + { + get + { + if (!string.IsNullOrEmpty(WorkHoursJson)) + return JsonSerializer.Deserialize(WorkHoursJson); + return new WorkHoursDto(); + } + set { WorkHoursJson = JsonSerializer.Serialize(value); } + } + + [JsonIgnore] + public string MapJson { get; set; } + public MapDto MapDto + { + get + { + if (!string.IsNullOrEmpty(MapJson)) + return JsonSerializer.Deserialize(MapJson); + return new MapDto(); + } + set { MapJson = JsonSerializer.Serialize(value); } + } +} + +public class BankDto +{ + public string AccountHolder { get; set; } + public string Branch { get; set; } + public string AccountNumber { get; set; } + public string Iban { get; set; } +} + +public class WorkHoursDto +{ + public string Weekday { get; set; } + public string Weekend { get; set; } + public string Whatsapp { get; set; } +} + +public class MapDto +{ + public string Title { get; set; } + public string Src { get; set; } + public string Width { get; set; } + public string Height { get; set; } + public string Style { get; set; } + public bool? AllowFullScreen { get; set; } + public string Loading { get; set; } + public string ReferrerPolicy { get; set; } +} \ No newline at end of file diff --git a/api/src/Kurs.Platform.Application/Public/PublicAppService.cs b/api/src/Kurs.Platform.Application/Public/PublicAppService.cs index 6844e155..06d81aae 100644 --- a/api/src/Kurs.Platform.Application/Public/PublicAppService.cs +++ b/api/src/Kurs.Platform.Application/Public/PublicAppService.cs @@ -15,6 +15,8 @@ using System.Linq; using Volo.Abp.Application.Dtos; using Kurs.Platform.Orders; using System.Text.Json; +using Kurs.Platform.Abouts; +using Kurs.Platform.Contacts; namespace Kurs.Platform.Public; @@ -30,6 +32,8 @@ public class PublicAppService : PlatformAppService private readonly IRepository _paymentMethodRepository; private readonly IRepository _installmentOptionRepository; private readonly IRepository _orderRepository; + private readonly IRepository _aboutRepository; + private readonly IRepository _contactRepository; public PublicAppService( IRepository serviceRepository, @@ -41,7 +45,9 @@ public class PublicAppService : PlatformAppService IRepository productRepository, IRepository paymentMethodRepository, IRepository installmentOptionRepository, - IRepository orderRepository + IRepository orderRepository, + IRepository aboutRepository, + IRepository contactRepository ) { _serviceRepository = serviceRepository; @@ -54,6 +60,8 @@ public class PublicAppService : PlatformAppService _paymentMethodRepository = paymentMethodRepository; _installmentOptionRepository = installmentOptionRepository; _orderRepository = orderRepository; + _aboutRepository = aboutRepository; + _contactRepository = contactRepository; } public async Task> GetServicesListAsync() @@ -268,4 +276,17 @@ public class PublicAppService : PlatformAppService PaymentMethod = entity.PaymentMethod }; } + + public async Task GetAboutAsync() + { + var entity = await _aboutRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(About)); + return ObjectMapper.Map(entity); + } + + public async Task GetContactAsync() + { + var entity = await _contactRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(Contact)); + + return ObjectMapper.Map(entity); + } } diff --git a/api/src/Kurs.Platform.Application/Public/PublicAutoMapperProfile.cs b/api/src/Kurs.Platform.Application/Public/PublicAutoMapperProfile.cs index b57c5afe..f7f16ab6 100644 --- a/api/src/Kurs.Platform.Application/Public/PublicAutoMapperProfile.cs +++ b/api/src/Kurs.Platform.Application/Public/PublicAutoMapperProfile.cs @@ -1,5 +1,7 @@ using AutoMapper; +using Kurs.Platform.Abouts; using Kurs.Platform.Blog; +using Kurs.Platform.Contacts; using Kurs.Platform.Demos; using Kurs.Platform.Entities; using Kurs.Platform.Orders; @@ -20,5 +22,7 @@ public class PublicAutoMapperProfile : Profile CreateMap(); CreateMap(); CreateMap(); + CreateMap(); + CreateMap(); } } diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/PlatformDataSeeder.cs b/api/src/Kurs.Platform.DbMigrator/Seeds/PlatformDataSeeder.cs index e90613c5..1a244d9b 100644 --- a/api/src/Kurs.Platform.DbMigrator/Seeds/PlatformDataSeeder.cs +++ b/api/src/Kurs.Platform.DbMigrator/Seeds/PlatformDataSeeder.cs @@ -66,6 +66,8 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency private readonly IRepository _customComponentRepository; private readonly IRepository _reportCategoriesRepository; private readonly IRepository _servicesRepository; + private readonly IRepository _aboutRepository; + private readonly IRepository _contactRepository; public PlatformDataSeeder( IRepository languages, @@ -104,7 +106,9 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency IRepository InstallmentOptionRepository, IRepository CustomComponentRepository, IRepository ReportCategoriesRepository, - IRepository ServicesRepository + IRepository ServicesRepository, + IRepository AboutRepository, + IRepository ContactRepository ) { _languages = languages; @@ -144,6 +148,8 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency _customComponentRepository = CustomComponentRepository; _reportCategoriesRepository = ReportCategoriesRepository; _servicesRepository = ServicesRepository; + _aboutRepository = AboutRepository; + _contactRepository = ContactRepository; } private static IConfigurationRoot BuildConfiguration() @@ -156,6 +162,243 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency return builder.Build(); } + + public async Task SeedCountyGroupsAsync() + { + var dbCtx = await _countryRepository.GetDbContextAsync(); + + // DB’de mevcut kodları set olarak al + var existingCodes = (await dbCtx.Set() + .Select(c => c.Name) + .ToListAsync()) + .ToHashSet(); + + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + + using var fs = File.OpenRead(Path.Combine("Seeds", "CountryGroups.json")); + + var buffer = new List(capacity: 1000); + var seenCodes = new HashSet(); // JSON içindeki duplicate’leri yakalamak için + + await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable(fs, options)) + { + if (item == null) continue; + if (string.IsNullOrWhiteSpace(item.Name)) continue; // boş kodları atla + + // hem DB’de hem JSON içinde duplicate engelle + if (!seenCodes.Add(item.Name) || existingCodes.Contains(item.Name)) + continue; + + buffer.Add(new CountryGroup( + Guid.NewGuid(), + item.Name + )); + + if (buffer.Count >= 1000) + { + await BulkCountryGroupInsertAsync(buffer); + buffer.Clear(); + } + } + + if (buffer.Count > 0) + { + await BulkCountryGroupInsertAsync(buffer); + } + } + + private async Task BulkCountryGroupInsertAsync(List entities) + { + var dbCtx = await _countryGroupRepository.GetDbContextAsync(); + await dbCtx.BulkInsertAsync(entities, new BulkConfig + { + BatchSize = 1000 + }); + } + + public async Task SeedCountriesAsync() + { + var dbCtx = await _countryRepository.GetDbContextAsync(); + + // DB’de mevcut kodları set olarak al + var existingCodes = (await dbCtx.Set() + .Select(c => c.Code) + .ToListAsync()) + .ToHashSet(); + + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + + using var fs = File.OpenRead(Path.Combine("Seeds", "Countries.json")); + + var buffer = new List(capacity: 1000); + var seenCodes = new HashSet(); // JSON içindeki duplicate’leri yakalamak için + + await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable(fs, options)) + { + if (item == null) continue; + if (string.IsNullOrWhiteSpace(item.Code)) continue; // boş kodları atla + + // hem DB’de hem JSON içinde duplicate engelle + if (!seenCodes.Add(item.Code) || existingCodes.Contains(item.Code)) + continue; + + buffer.Add(new Country( + Guid.NewGuid(), + item.Code, + item.Name, + item.GroupName, + item.CurrencyCode, + item.PhoneCode, + item.TaxLabel + )); + + if (buffer.Count >= 1000) + { + await BulkCountryInsertAsync(buffer); + buffer.Clear(); + } + } + + if (buffer.Count > 0) + { + await BulkCountryInsertAsync(buffer); + } + } + + private async Task BulkCountryInsertAsync(List entities) + { + var dbCtx = await _countryRepository.GetDbContextAsync(); + await dbCtx.BulkInsertAsync(entities, new BulkConfig + { + BatchSize = 1000 + }); + } + + public async Task SeedCitiesAsync() + { + var dbCtx = await _cityRepository.GetDbContextAsync(); + + // 1. Mevcut kayıtları çek (tek sorguda) + var existingCities = await dbCtx.Set() + .Select(d => new { d.Code }) + .ToListAsync(); + + var existingSet = existingCities + .Select(d => d.Code) + .ToHashSet(); + + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + + // 2. JSON’u stream et + using FileStream fs = File.OpenRead(Path.Combine("Seeds", "Cities.json")); + + var buffer = new List(capacity: 5000); + await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable(fs, options)) + { + if (item == null) continue; + + var key = $"{item.CountryCode}.{item.Code}"; + if (existingSet.Contains(key)) continue; // duplicate kontrolü + + buffer.Add(new City( + Guid.NewGuid(), + item.CountryCode, + item.Name, + key, + item.PlateCode + )); + + if (buffer.Count >= 1000) // 3. Batch + { + await BulkCityInsertAsync(buffer); + buffer.Clear(); + } + } + + if (buffer.Count > 0) + { + await BulkCityInsertAsync(buffer); + } + } + + private async Task BulkCityInsertAsync(List entities) + { + var dbCtx = await _cityRepository.GetDbContextAsync(); + await dbCtx.BulkInsertAsync(entities, new BulkConfig + { + PreserveInsertOrder = true, + SetOutputIdentity = true, + BatchSize = 1000 + }); + } + + public async Task SeedDistrictsAsync() + { + var dbCtx = await _districtRepository.GetDbContextAsync(); + + // 1. Mevcut kayıtları çek (tek sorguda) + var existingDistricts = await dbCtx.Set() + .Select(d => new { d.CountryCode, d.CityCode, d.Name, d.Township, d.Street, d.ZipCode }) + .ToListAsync(); + + var existingSet = existingDistricts + .Select(d => $"{d.CountryCode}:{d.CityCode}:{d.Name}:{d.Township}:{d.Street}:{d.ZipCode}") + .ToHashSet(); + + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + + // 2. JSON’u stream et + using FileStream fs = File.OpenRead(Path.Combine("Seeds", "Districts.json")); + + var buffer = new List(capacity: 5000); + await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable(fs, options)) + { + if (item == null) continue; + + var key = $"{item.CountryCode}:{item.CountryCode}.{item.CityCode}:{item.Name}:{item.Township}:{item.Street}:{item.ZipCode}"; + var cityCode = $"{item.CountryCode}.{item.CityCode}"; + if (existingSet.Contains(key)) continue; + + buffer.Add(new District( + Guid.NewGuid(), + item.CountryCode, + cityCode, + item.Name, + item.Township, + item.Street, + item.ZipCode + )); + + if (buffer.Count >= 5000) // 3. Batch + { + await BulkDistrictInsertAsync(buffer); + buffer.Clear(); + } + } + + if (buffer.Count > 0) + { + await BulkDistrictInsertAsync(buffer); + } + } + + private async Task BulkDistrictInsertAsync(List entities) + { + var dbCtx = await _districtRepository.GetDbContextAsync(); + await dbCtx.BulkInsertAsync(entities, new BulkConfig + { + PreserveInsertOrder = true, + SetOutputIdentity = true, + BatchSize = 5000 + }); + } + public async Task SeedAsync(DataSeedContext context) { var settings = await _settings.GetListAsync(); @@ -172,13 +415,7 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency .Build(); var items = configuration.Get(); - await SeedCountyGroupsAsync(); - - await SeedCountriesAsync(); - - await SeedCitiesAsync(); - - await SeedDistrictsAsync(); + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; foreach (var item in items.Charts) { @@ -758,240 +995,46 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency )); } } - } - public async Task SeedCountyGroupsAsync() - { - var dbCtx = await _countryRepository.GetDbContextAsync(); - - // DB’de mevcut kodları set olarak al - var existingCodes = (await dbCtx.Set() - .Select(c => c.Name) - .ToListAsync()) - .ToHashSet(); - - var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; - - using var fs = File.OpenRead(Path.Combine("Seeds", "CountryGroups.json")); - - var buffer = new List(capacity: 1000); - var seenCodes = new HashSet(); // JSON içindeki duplicate’leri yakalamak için - - await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable(fs, options)) + foreach (var item in items.Abouts) { - if (item == null) continue; - if (string.IsNullOrWhiteSpace(item.Name)) continue; // boş kodları atla - - // hem DB’de hem JSON içinde duplicate engelle - if (!seenCodes.Add(item.Name) || existingCodes.Contains(item.Name)) - continue; - - buffer.Add(new CountryGroup( - Guid.NewGuid(), - item.Name - )); - - if (buffer.Count >= 1000) + var exists = await _aboutRepository.FirstOrDefaultAsync(); + if (exists == null) { - await BulkCountryGroupInsertAsync(buffer); - buffer.Clear(); + await _aboutRepository.InsertAsync(new About( + Guid.NewGuid(), + JsonSerializer.Serialize(item.Stats), + JsonSerializer.Serialize(item.Descriptions), + JsonSerializer.Serialize(item.Sections) + )); } } - if (buffer.Count > 0) + foreach (var item in items.Contacts) { - await BulkCountryGroupInsertAsync(buffer); - } - } - - private async Task BulkCountryGroupInsertAsync(List entities) - { - var dbCtx = await _countryGroupRepository.GetDbContextAsync(); - await dbCtx.BulkInsertAsync(entities, new BulkConfig - { - BatchSize = 1000 - }); - } - - public async Task SeedCountriesAsync() - { - var dbCtx = await _countryRepository.GetDbContextAsync(); - - // DB’de mevcut kodları set olarak al - var existingCodes = (await dbCtx.Set() - .Select(c => c.Code) - .ToListAsync()) - .ToHashSet(); - - var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; - - using var fs = File.OpenRead(Path.Combine("Seeds", "Countries.json")); - - var buffer = new List(capacity: 1000); - var seenCodes = new HashSet(); // JSON içindeki duplicate’leri yakalamak için - - await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable(fs, options)) - { - if (item == null) continue; - if (string.IsNullOrWhiteSpace(item.Code)) continue; // boş kodları atla - - // hem DB’de hem JSON içinde duplicate engelle - if (!seenCodes.Add(item.Code) || existingCodes.Contains(item.Code)) - continue; - - buffer.Add(new Country( - Guid.NewGuid(), - item.Code, - item.Name, - item.GroupName, - item.CurrencyCode, - item.PhoneCode, - item.TaxLabel - )); - - if (buffer.Count >= 1000) + var exists = await _contactRepository.FirstOrDefaultAsync(); + if (exists == null) { - await BulkCountryInsertAsync(buffer); - buffer.Clear(); + await _contactRepository.InsertAsync(new Contact( + Guid.NewGuid(), + item.Address, + item.Phone, + item.Email, + item.Location, + item.TaxNumber, + JsonSerializer.Serialize(item.Bank), + JsonSerializer.Serialize(item.WorkHour), + JsonSerializer.Serialize(item.Map) + )); } } - if (buffer.Count > 0) - { - await BulkCountryInsertAsync(buffer); - } - } + await SeedCountyGroupsAsync(); - private async Task BulkCountryInsertAsync(List entities) - { - var dbCtx = await _countryRepository.GetDbContextAsync(); - await dbCtx.BulkInsertAsync(entities, new BulkConfig - { - BatchSize = 1000 - }); - } + await SeedCountriesAsync(); - public async Task SeedCitiesAsync() - { - var dbCtx = await _cityRepository.GetDbContextAsync(); + await SeedCitiesAsync(); - // 1. Mevcut kayıtları çek (tek sorguda) - var existingCities = await dbCtx.Set() - .Select(d => new { d.Code }) - .ToListAsync(); - - var existingSet = existingCities - .Select(d => d.Code) - .ToHashSet(); - - var options = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }; - - // 2. JSON’u stream et - using FileStream fs = File.OpenRead(Path.Combine("Seeds", "Cities.json")); - - var buffer = new List(capacity: 5000); - await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable(fs, options)) - { - if (item == null) continue; - - var key = $"{item.Code}"; - if (existingSet.Contains(key)) continue; // duplicate kontrolü - - buffer.Add(new City( - Guid.NewGuid(), - item.CountryCode, - item.Name, - $"{item.CountryCode}.{item.Code}", - item.PlateCode - )); - - if (buffer.Count >= 1000) // 3. Batch - { - await BulkCityInsertAsync(buffer); - buffer.Clear(); - } - } - - if (buffer.Count > 0) - { - await BulkCityInsertAsync(buffer); - } - } - - private async Task BulkCityInsertAsync(List entities) - { - var dbCtx = await _cityRepository.GetDbContextAsync(); - await dbCtx.BulkInsertAsync(entities, new BulkConfig - { - PreserveInsertOrder = true, - SetOutputIdentity = true, - BatchSize = 1000 - }); - } - - public async Task SeedDistrictsAsync() - { - var dbCtx = await _districtRepository.GetDbContextAsync(); - - // 1. Mevcut kayıtları çek (tek sorguda) - var existingDistricts = await dbCtx.Set() - .Select(d => new { d.CountryCode, d.CityCode, d.Name, d.Township, d.Street, d.ZipCode }) - .ToListAsync(); - - var existingSet = existingDistricts - .Select(d => $"{d.CountryCode}:{d.CityCode}:{d.Name}:{d.Township}:{d.Street}:{d.ZipCode}") - .ToHashSet(); - - var options = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }; - - // 2. JSON’u stream et - using FileStream fs = File.OpenRead(Path.Combine("Seeds", "Districts.json")); - - var buffer = new List(capacity: 5000); - await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable(fs, options)) - { - if (item == null) continue; - - var key = $"{item.CountryCode}:{item.CityCode}:{item.Name}:{item.Township}:{item.Street}:{item.ZipCode}"; - if (existingSet.Contains(key)) continue; // duplicate kontrolü - - buffer.Add(new District( - Guid.NewGuid(), - item.CountryCode, - $"{item.CountryCode}.{item.CityCode}", - item.Name, - item.Township, - item.Street, - item.ZipCode - )); - - if (buffer.Count >= 5000) // 3. Batch - { - await BulkDistrictInsertAsync(buffer); - buffer.Clear(); - } - } - - if (buffer.Count > 0) - { - await BulkDistrictInsertAsync(buffer); - } - } - - private async Task BulkDistrictInsertAsync(List entities) - { - var dbCtx = await _districtRepository.GetDbContextAsync(); - await dbCtx.BulkInsertAsync(entities, new BulkConfig - { - PreserveInsertOrder = true, - SetOutputIdentity = true, - BatchSize = 5000 - }); + await SeedDistrictsAsync(); } } diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/SeederData.json b/api/src/Kurs.Platform.DbMigrator/Seeds/SeederData.json index 99e0f291..94c81ba5 100644 --- a/api/src/Kurs.Platform.DbMigrator/Seeds/SeederData.json +++ b/api/src/Kurs.Platform.DbMigrator/Seeds/SeederData.json @@ -17360,5 +17360,88 @@ "Public.services.support.sms.features.api" ] } + ], + "Abouts": [ + { + "stats": [ + { + "icon": "FaUsers", + "value": 300, + "labelKey": "Public.about.stats.clients", + "useCounter": true, + "counterEnd": 300, + "counterSuffix": "+", + "counterDuration": 2500 + }, + { + "icon": "FaAward", + "value": 20, + "labelKey": "Public.about.stats.experience", + "useCounter": true, + "counterEnd": 20, + "counterSuffix": "+", + "counterDuration": 2000 + }, + { + "icon": "FaClock", + "value": "7/24", + "labelKey": "Public.about.stats.support", + "useCounter": false + }, + { + "icon": "FaGlobe", + "value": 3, + "labelKey": "Public.about.stats.countries", + "useCounter": true, + "counterEnd": 3, + "counterDuration": 1500 + } + ], + "descriptions": [ + "Public.about.description.part1", + "Public.about.description.motto", + "Public.about.description.part2", + "Public.about.description.closing" + ], + "sections": [ + { + "key": "Public.about.mission", + "descKey": "Public.about.mission.desc" + }, + { + "key": "Public.about.vision", + "descKey": "Public.about.vision.desc" + } + ] + } + ], + "Contacts": [ + { + "address": "Public.contact.address.full", + "phone": "+90 (544) 769 7 638", + "email": "destek@sozsoft.com", + "location": "Kozyatağı", + "taxNumber": "32374982750", + "bank": { + "accountHolder": "Özlem Öztürk", + "branch": "03663 / Enpara", + "accountNumber": "73941177", + "iban": "TR65 0011 1000 0000 0073 9411 77" + }, + "workHour": { + "weekday": "Public.contact.workHours.weekday", + "weekend": "Public.contact.workHours.weekend", + "whatsapp": "Public.contact.workHours.whatsapp" + }, + "map": { + "title": "Public.contact.location", + "src": "https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3006.209566407676!2d28.757000999999992!3d41.10811400000001!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x0%3A0x8a0bbbdfcfd3fd24!2zU8O2enNvZnQ!5e0!3m2!1str!2str!4v1450816303558", + "width": "100%", + "height": "700", + "allowFullScreen": true, + "loading": "lazy", + "referrerPolicy": "no-referrer-when-downgrade" + } + } ] } diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/SeederDto.cs b/api/src/Kurs.Platform.DbMigrator/Seeds/SeederDto.cs index 05e3dcff..fe165879 100644 --- a/api/src/Kurs.Platform.DbMigrator/Seeds/SeederDto.cs +++ b/api/src/Kurs.Platform.DbMigrator/Seeds/SeederDto.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using Kurs.Languages.Entities; using Kurs.Platform.Charts.Dto; +using Kurs.Platform.Contacts; using Kurs.Platform.Entities; using Kurs.Platform.ListForms; using Kurs.Settings.Entities; +using static Kurs.Platform.Abouts.AboutDto; namespace Kurs.Platform.Seeds; @@ -42,6 +44,8 @@ public class SeederDto public List CustomComponents { get; set; } public List ReportCategories { get; set; } public List Services { get; set; } + public List Abouts { get; set; } + public List Contacts { get; set; } } public class ChartsSeedDto @@ -301,3 +305,22 @@ public class ServiceSeedDto public string Type { get; set; } public string[] Features { get; set; } } + +public class AboutSeedDto +{ + public List Stats { get; set; } + public List Descriptions { get; set; } + public List Sections { get; set; } +} + +public class ContactSeedDto +{ + public string Address { get; set; } + public string Phone { get; set; } + public string Email { get; set; } + public string Location { get; set; } + public string TaxNumber { get; set; } + public BankDto Bank { get; set; } + public WorkHoursDto WorkHour { get; set; } + public MapDto Map { get; set; } +} \ No newline at end of file diff --git a/api/src/Kurs.Platform.Domain/Entities/About.cs b/api/src/Kurs.Platform.Domain/Entities/About.cs new file mode 100644 index 00000000..b42b9c2e --- /dev/null +++ b/api/src/Kurs.Platform.Domain/Entities/About.cs @@ -0,0 +1,28 @@ +using System; +using Volo.Abp.Domain.Entities.Auditing; +using Volo.Abp.MultiTenancy; + +namespace Kurs.Platform.Entities; + +public class About : FullAuditedEntity, IMultiTenant +{ + public Guid? TenantId { get; set; } + + public string StatsJson { get; set; } + public string DescriptionsJson { get; set; } + public string SectionsJson { get; set; } + + protected About() { } + + public About( + Guid id, + string statsJson, + string descriptionsJson, + string sectionsJson + ) : base(id) + { + StatsJson = statsJson; + DescriptionsJson = descriptionsJson; + SectionsJson = sectionsJson; + } +} \ No newline at end of file diff --git a/api/src/Kurs.Platform.Domain/Entities/Contact.cs b/api/src/Kurs.Platform.Domain/Entities/Contact.cs new file mode 100644 index 00000000..be92cd95 --- /dev/null +++ b/api/src/Kurs.Platform.Domain/Entities/Contact.cs @@ -0,0 +1,45 @@ +using System; +using Volo.Abp.Domain.Entities.Auditing; +using Volo.Abp.MultiTenancy; + +namespace Kurs.Platform.Entities; + +public class Contact : FullAuditedEntity, IMultiTenant +{ + public Guid? TenantId { get; set; } + + public string Address { get; set; } + public string Phone { get; set; } + public string Email { get; set; } + public string Location { get; set; } + public string TaxNumber { get; set; } + + // JSON string kolonlar + public string BankJson { get; set; } + public string WorkHoursJson { get; set; } + public string MapJson { get; set; } + + protected Contact() { } + + public Contact( + Guid id, + string address, + string phone, + string email, + string location, + string taxNumber, + string bankJson, + string workHoursJson, + string mapJson + ) : base(id) + { + Address = address; + Phone = phone; + Email = email; + Location = location; + TaxNumber = taxNumber; + BankJson = bankJson; + WorkHoursJson = workHoursJson; + MapJson = mapJson; + } +} \ No newline at end of file diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs b/api/src/Kurs.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs index 91308ece..d623b561 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs @@ -80,6 +80,9 @@ public class PlatformDbContext : public DbSet PaymentMethods { get; set; } public DbSet InstallmentOptions { get; set; } + public DbSet Abouts { get; set; } + public DbSet Contacts { get; set; } + public DbSet Orders { get; set; } public DbSet OrderItems { get; set; } @@ -832,5 +835,33 @@ public class PlatformDbContext : ) ); }); + + builder.Entity(b => + { + b.ToTable(PlatformConsts.DbTablePrefix + nameof(About), PlatformConsts.DbSchema); + b.ConfigureByConvention(); + + // JSON alanlar + b.Property(x => x.StatsJson).HasColumnType("nvarchar(max)"); + b.Property(x => x.DescriptionsJson).HasColumnType("nvarchar(max)"); + b.Property(x => x.SectionsJson).HasColumnType("nvarchar(max)"); + }); + + builder.Entity(b => + { + b.ToTable(PlatformConsts.DbTablePrefix + nameof(Contact), PlatformConsts.DbSchema); + b.ConfigureByConvention(); + + b.Property(x => x.Address).HasMaxLength(500); + b.Property(x => x.Phone).HasMaxLength(50); + b.Property(x => x.Email).HasMaxLength(150); + b.Property(x => x.Location).HasMaxLength(150); + b.Property(x => x.TaxNumber).HasMaxLength(50); + + // JSON alanlar nvarchar(max) olarak + b.Property(x => x.BankJson).HasColumnType("nvarchar(max)"); + b.Property(x => x.WorkHoursJson).HasColumnType("nvarchar(max)"); + b.Property(x => x.MapJson).HasColumnType("nvarchar(max)"); + }); } } diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250820190627_GenelDuzenleme.Designer.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250821081019_GenelDuzenleme.Designer.cs similarity index 98% rename from api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250820190627_GenelDuzenleme.Designer.cs rename to api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250821081019_GenelDuzenleme.Designer.cs index ae943e7b..4af569e0 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250820190627_GenelDuzenleme.Designer.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250821081019_GenelDuzenleme.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Kurs.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20250820190627_GenelDuzenleme")] + [Migration("20250821081019_GenelDuzenleme")] partial class GenelDuzenleme { /// @@ -650,6 +650,59 @@ namespace Kurs.Platform.Migrations b.ToTable("PNotificationRule", (string)null); }); + modelBuilder.Entity("Kurs.Platform.Entities.About", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uniqueidentifier") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uniqueidentifier") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("datetime2") + .HasColumnName("DeletionTime"); + + b.Property("DescriptionsJson") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("datetime2") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uniqueidentifier") + .HasColumnName("LastModifierId"); + + b.Property("SectionsJson") + .HasColumnType("nvarchar(max)"); + + b.Property("StatsJson") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("PAbout", (string)null); + }); + modelBuilder.Entity("Kurs.Platform.Entities.AiBot", b => { b.Property("Id") @@ -1506,6 +1559,79 @@ namespace Kurs.Platform.Migrations b.ToTable("PCity", (string)null); }); + modelBuilder.Entity("Kurs.Platform.Entities.Contact", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("BankJson") + .HasColumnType("nvarchar(max)"); + + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uniqueidentifier") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uniqueidentifier") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("datetime2") + .HasColumnName("DeletionTime"); + + b.Property("Email") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("datetime2") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uniqueidentifier") + .HasColumnName("LastModifierId"); + + b.Property("Location") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("MapJson") + .HasColumnType("nvarchar(max)"); + + b.Property("Phone") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TaxNumber") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.Property("WorkHoursJson") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("PContact", (string)null); + }); + modelBuilder.Entity("Kurs.Platform.Entities.Country", b => { b.Property("Id") diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250820190627_GenelDuzenleme.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250821081019_GenelDuzenleme.cs similarity index 91% rename from api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250820190627_GenelDuzenleme.cs rename to api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250821081019_GenelDuzenleme.cs index bb5a570e..0fbfacaf 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250820190627_GenelDuzenleme.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250821081019_GenelDuzenleme.cs @@ -173,6 +173,55 @@ namespace Kurs.Platform.Migrations table: "PCity", columns: new[] { "CountryCode", "Code" }); + migrationBuilder.CreateTable( + name: "PAbout", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + StatsJson = table.Column(type: "nvarchar(max)", nullable: true), + DescriptionsJson = table.Column(type: "nvarchar(max)", nullable: true), + SectionsJson = table.Column(type: "nvarchar(max)", nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PAbout", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PContact", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + Address = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + Phone = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + Email = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: true), + Location = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: true), + TaxNumber = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + BankJson = table.Column(type: "nvarchar(max)", nullable: true), + WorkHoursJson = table.Column(type: "nvarchar(max)", nullable: true), + MapJson = table.Column(type: "nvarchar(max)", nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PContact", x => x.Id); + }); + migrationBuilder.CreateTable( name: "PDemo", columns: table => new @@ -598,6 +647,12 @@ namespace Kurs.Platform.Migrations name: "FK_PUom_PUomCategory_UomCategoryId", table: "PUom"); + migrationBuilder.DropTable( + name: "PAbout"); + + migrationBuilder.DropTable( + name: "PContact"); + migrationBuilder.DropTable( name: "PDemo"); diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index 7d7028c8..3872a91d 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -647,6 +647,59 @@ namespace Kurs.Platform.Migrations b.ToTable("PNotificationRule", (string)null); }); + modelBuilder.Entity("Kurs.Platform.Entities.About", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uniqueidentifier") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uniqueidentifier") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("datetime2") + .HasColumnName("DeletionTime"); + + b.Property("DescriptionsJson") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("datetime2") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uniqueidentifier") + .HasColumnName("LastModifierId"); + + b.Property("SectionsJson") + .HasColumnType("nvarchar(max)"); + + b.Property("StatsJson") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("PAbout", (string)null); + }); + modelBuilder.Entity("Kurs.Platform.Entities.AiBot", b => { b.Property("Id") @@ -1503,6 +1556,79 @@ namespace Kurs.Platform.Migrations b.ToTable("PCity", (string)null); }); + modelBuilder.Entity("Kurs.Platform.Entities.Contact", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("BankJson") + .HasColumnType("nvarchar(max)"); + + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uniqueidentifier") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uniqueidentifier") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("datetime2") + .HasColumnName("DeletionTime"); + + b.Property("Email") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("datetime2") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uniqueidentifier") + .HasColumnName("LastModifierId"); + + b.Property("Location") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("MapJson") + .HasColumnType("nvarchar(max)"); + + b.Property("Phone") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TaxNumber") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.Property("WorkHoursJson") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("PContact", (string)null); + }); + modelBuilder.Entity("Kurs.Platform.Entities.Country", b => { b.Property("Id") diff --git a/ui/dev-dist/sw.js b/ui/dev-dist/sw.js index 7cedb1a4..1c34a8f7 100644 --- a/ui/dev-dist/sw.js +++ b/ui/dev-dist/sw.js @@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict'; "revision": "3ca0b8505b4bec776b69afdba2768812" }, { "url": "index.html", - "revision": "0.vt922hqbnl8" + "revision": "0.2cgibr83seg" }], {}); workbox.cleanupOutdatedCaches(); workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { diff --git a/ui/src/proxy/about/models.ts b/ui/src/proxy/about/models.ts new file mode 100644 index 00000000..4a9dc172 --- /dev/null +++ b/ui/src/proxy/about/models.ts @@ -0,0 +1,22 @@ +export interface AboutDto { + id: string + tenantId?: string + statsDto: StatDto[] + descriptionsDto: string[] + sectionsDto: SectionDto[] +} + +export interface StatDto { + icon: string + value: string + labelKey: string + useCounter?: boolean + counterEnd?: number + counterSuffix?: string + counterDuration?: number +} + +export interface SectionDto { + key: string + descKey: string +} diff --git a/ui/src/proxy/contact/models.ts b/ui/src/proxy/contact/models.ts new file mode 100644 index 00000000..795956df --- /dev/null +++ b/ui/src/proxy/contact/models.ts @@ -0,0 +1,34 @@ +export interface ContactDto { + id: string + address: string + phone: string + email: string + location: string + taxNumber: string + bankDto: BankDto + workHoursDto: WorkHoursDto + mapDto: MapDto +} + +export interface BankDto { + accountHolder: string + branch: string + accountNumber: string + iban: string +} + +export interface WorkHoursDto { + weekday: string + weekend: string + whatsapp: string +} + +export interface MapDto { + title: string + src: string + width: string + height: string + allowFullScreen?: boolean + loading: string + referrerPolicy: string +} diff --git a/ui/src/services/about.ts b/ui/src/services/about.ts new file mode 100644 index 00000000..bbdade58 --- /dev/null +++ b/ui/src/services/about.ts @@ -0,0 +1,12 @@ +import { AboutDto } from '@/proxy/about/models' +import apiService from './api.service' + +export function getAbout() { + return apiService.fetchData( + { + method: 'GET', + url: '/api/app/public/about', + }, + { apiName: 'Default' }, + ) +} diff --git a/ui/src/services/contact.ts b/ui/src/services/contact.ts new file mode 100644 index 00000000..1ab8a1e4 --- /dev/null +++ b/ui/src/services/contact.ts @@ -0,0 +1,12 @@ +import apiService from './api.service' +import { ContactDto } from '@/proxy/contact/models' + +export function getContact() { + return apiService.fetchData( + { + method: 'GET', + url: '/api/app/public/contact', + }, + { apiName: 'Default' }, + ) +} diff --git a/ui/src/utils/hooks/useCountUp.ts b/ui/src/utils/hooks/useCountUp.ts deleted file mode 100644 index ebcff86b..00000000 --- a/ui/src/utils/hooks/useCountUp.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { useState, useEffect, useRef } from 'react' - -interface UseCountUpOptions { - start?: number - end: number - duration?: number - suffix?: string - prefix?: string -} - -export const useCountUp = ({ - start = 0, - end, - duration = 2000, - suffix = '', - prefix = '', -}: UseCountUpOptions) => { - const [count, setCount] = useState(start) - const [isInView, setIsInView] = useState(false) - const elementRef = useRef(null) - - useEffect(() => { - const observer = new IntersectionObserver( - ([entry]) => { - if (entry.isIntersecting && !isInView) { - setIsInView(true) - } - }, - { threshold: 0.1 }, - ) - - if (elementRef.current) { - observer.observe(elementRef.current) - } - - return () => observer.disconnect() - }, [isInView]) - - useEffect(() => { - if (!isInView) return - - let startTime: number - const animateCount = (timestamp: number) => { - if (!startTime) startTime = timestamp - - const progress = Math.min((timestamp - startTime) / duration, 1) - const currentCount = Math.floor(progress * (end - start) + start) - - setCount(currentCount) - - if (progress < 1) { - requestAnimationFrame(animateCount) - } - } - - requestAnimationFrame(animateCount) - }, [isInView, start, end, duration]) - - const displayValue = `${prefix}${count}${suffix}` - - return { count, displayValue, elementRef } -} diff --git a/ui/src/views/public/About.tsx b/ui/src/views/public/About.tsx index d4b845f8..23d53c73 100644 --- a/ui/src/views/public/About.tsx +++ b/ui/src/views/public/About.tsx @@ -1,108 +1,44 @@ -import React from 'react' -import { - FaUsers, - FaAward, - FaClock, - FaGlobe -} from 'react-icons/fa'; +import React, { useEffect, useState } from 'react' import { useLocalization } from '@/utils/hooks/useLocalization' -import { useCountUp } from '@/utils/hooks/useCountUp' import { Helmet } from 'react-helmet' -import { IconType } from 'react-icons' - -interface StatItem { - icon: IconType - value: string | number - labelKey: string - useCounter?: boolean - counterEnd?: number - counterSuffix?: string - counterDuration?: number -} - -interface ContentSection { - key: string - descKey: string -} - -interface AboutPageData { - stats: StatItem[] - descriptions: string[] - sections: ContentSection[] -} +import navigationIcon from '@/configs/navigation-icon.config' +import { AboutDto } from '@/proxy/about/models' +import { getAbout } from '@/services/about' const About: React.FC = () => { const { translate } = useLocalization() + const [loading, setLoading] = useState(true) + const [about, setAbout] = useState() - // About page data configuration - const aboutPageData: AboutPageData = { - stats: [ - { - icon: FaUsers, - value: 300, - labelKey: '::Public.about.stats.clients', - useCounter: true, - counterEnd: 300, - counterSuffix: '+', - counterDuration: 2500 - }, - { - icon: FaAward, - value: 20, - labelKey: '::Public.about.stats.experience', - useCounter: true, - counterEnd: 20, - counterSuffix: '+', - counterDuration: 2000 - }, - { - icon: FaClock, - value: '7/24', - labelKey: '::Public.about.stats.support', - useCounter: false - }, - { - icon: FaGlobe, - value: 3, - labelKey: '::Public.about.stats.countries', - useCounter: true, - counterEnd: 3, - counterDuration: 1500 - } - ], - descriptions: [ - '::Public.about.description.part1', - '::Public.about.description.motto', - '::Public.about.description.part2', - '::Public.about.description.closing' - ], - sections: [ - { - key: '::Public.about.mission', - descKey: '::Public.about.mission.desc' - }, - { - key: '::Public.about.vision', - descKey: '::Public.about.vision.desc' - } - ] + const iconColors = [ + 'text-blue-600', + 'text-red-600', + 'text-green-600', + 'text-purple-600', + 'text-yellow-600', + 'text-indigo-600', + ] + + function getRandomColor() { + return iconColors[Math.floor(Math.random() * iconColors.length)] } - // Counter animations based on data - const clientsCount = useCountUp({ - end: aboutPageData.stats[0].counterEnd!, - suffix: aboutPageData.stats[0].counterSuffix, - duration: aboutPageData.stats[0].counterDuration! - }) - const experienceCount = useCountUp({ - end: aboutPageData.stats[1].counterEnd!, - suffix: aboutPageData.stats[1].counterSuffix, - duration: aboutPageData.stats[1].counterDuration! - }) - const countriesCount = useCountUp({ - end: aboutPageData.stats[3].counterEnd!, - duration: aboutPageData.stats[3].counterDuration! - }) + useEffect(() => { + setLoading(true) + + const fetchServices = async () => { + try { + const result = await getAbout() + setAbout(result.data) + } catch (error) { + console.error('About alınırken hata oluştu:', error) + } finally { + setLoading(false) + } + } + + fetchServices() + }, []) return ( <> @@ -136,32 +72,19 @@ const About: React.FC = () => {
- {aboutPageData.stats.map((stat, index) => { - const IconComponent = stat.icon + {about?.statsDto.map((stat, index) => { + const IconComponent = navigationIcon[stat.icon || ''] + let displayValue = stat.value let elementRef = undefined - // Handle counter values - if (stat.useCounter) { - if (index === 0) { - displayValue = clientsCount.displayValue - elementRef = clientsCount.elementRef - } else if (index === 1) { - displayValue = experienceCount.displayValue - elementRef = experienceCount.elementRef - } else if (index === 3) { - displayValue = countriesCount.displayValue - elementRef = countriesCount.elementRef - } - } - return ( -
- -
- {displayValue} -
-
{translate(stat.labelKey)}
+
+ {IconComponent && ( + + )} +
{displayValue}
+
{translate('::' + stat.labelKey)}
) })} @@ -174,28 +97,24 @@ const About: React.FC = () => {
-

- {translate(aboutPageData.descriptions[0])} -

+

{translate('::' + about?.descriptionsDto[0])}

- {translate(aboutPageData.descriptions[1])} -

-

- {translate(aboutPageData.descriptions[2])} + {translate('::' + about?.descriptionsDto[1])}

+

{translate('::' + about?.descriptionsDto[2])}

- {translate(aboutPageData.descriptions[3])} + {translate('::' + about?.descriptionsDto[3])}

- {aboutPageData.sections.map((section, index) => ( + {about?.sectionsDto.map((section, index) => (

- {translate(section.key)} + {translate('::' + section.key)}

-

{translate(section.descKey)}

+

{translate('::' + section.descKey)}

))}
diff --git a/ui/src/views/public/Contact.tsx b/ui/src/views/public/Contact.tsx index 097d5a92..29a8b1f2 100644 --- a/ui/src/views/public/Contact.tsx +++ b/ui/src/views/public/Contact.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' import { FaMailBulk, FaPhone, @@ -7,13 +7,35 @@ import { FaBuilding, FaCalendarAlt, FaCalendarCheck, - FaRegComment -} from 'react-icons/fa'; + FaRegComment, +} from 'react-icons/fa' import { useLocalization } from '@/utils/hooks/useLocalization' import { Helmet } from 'react-helmet' +import { ContactDto } from '@/proxy/contact/models' +import { getContact } from '@/services/contact' const Contact: React.FC = () => { const { translate } = useLocalization() + const [loading, setLoading] = useState(true) + const [contact, setContact] = useState() + + useEffect(() => { + setLoading(true) + + const fetchServices = async () => { + try { + const result = await getContact() + + setContact(result.data) + } catch (error) { + console.error('About alınırken hata oluştu:', error) + } finally { + setLoading(false) + } + } + + fetchServices() + }, []) return (
@@ -42,51 +64,51 @@ const Contact: React.FC = () => {
+ {/* Stats Section */}
- {/* Contact Information */}

{translate('::Public.contact.info.title')}

-
-
- +
+
+
-

{translate('::Public.contact.address.full')}

+

{translate('::' + contact?.address)}

-
- +
+
-

+90 (544) 769 7 638

+

{contact?.phone}

-
- + -
- +
+
-

Kozyatağı

+

{contact?.location}

-
- +
+
-

32374982750

+

{contact?.taxNumber}

@@ -96,21 +118,19 @@ const Contact: React.FC = () => {

{translate('::Public.contact.bank.title')}

-
+
Enpara Logo
-

Özlem Öztürk

-

- 03663 / Enpara -
- 73941177 -
- TR65 0011 1000 0000 0073 9411 77 -

+

+ {contact?.bankDto.accountHolder} +

+

{contact?.bankDto.branch}

+

{contact?.bankDto.accountNumber}

+

{contact?.bankDto.iban}

@@ -120,28 +140,24 @@ const Contact: React.FC = () => {

{translate('::Public.contact.workHours')}

-
-
-
-
- -

- {translate('::Public.contact.workHours.weekday')} -

-
-
- -

- {translate('::Public.contact.workHours.weekend')} -

-
-
- -

- {translate('::Public.contact.workHours.whatsapp')} -

-
-
+
+
+ +

+ {translate('::' + contact?.workHoursDto.weekday)} +

+
+
+ +

+ {translate('::' + contact?.workHoursDto.weekend)} +

+
+
+ +

+ {translate('::' + contact?.workHoursDto.whatsapp)} +

@@ -150,17 +166,19 @@ const Contact: React.FC = () => { {/* Map Section */}

- {translate('::Public.contact.location')} + {translate('::' + contact?.mapDto.title)}

diff --git a/ui/src/views/public/Services.tsx b/ui/src/views/public/Services.tsx index 5959171a..2bc30919 100644 --- a/ui/src/views/public/Services.tsx +++ b/ui/src/views/public/Services.tsx @@ -19,128 +19,7 @@ import navigationIcon from '@/configs/navigation-icon.config' const Services: React.FC = () => { const { translate } = useLocalization() const [services, setServices] = useState([]) - - // const services: ServiceDto[] = [ - // { - // icon: , - // title: translate('::Public.services.software.title'), - // description: translate('::Public.services.software.desc'), - // type: 'service', - // features: [ - // translate('::Public.services.software.features.analysis'), - // translate('::Public.services.software.features.design'), - // translate('::Public.services.software.features.development'), - // translate('::Public.services.software.features.testing'), - // translate('::Public.services.software.features.maintenance'), - // ], - // }, - // { - // icon: , - // title: translate('::Public.services.web.title'), - // description: translate('::Public.services.web.desc'), - // type: 'service', - // features: [ - // translate('::Public.services.web.features.frontend'), - // translate('::Public.services.web.features.backend'), - // translate('::Public.services.web.features.api'), - // translate('::Public.services.web.features.seo'), - // translate('::Public.services.web.features.performance'), - // ], - // }, - // { - // icon: , - // title: translate('::Public.services.mobile.title'), - // description: translate('::Public.services.mobile.desc'), - // type: 'service', - // features: [ - // translate('::Public.services.mobile.features.design'), - // translate('::Public.services.mobile.features.native'), - // translate('::Public.services.mobile.features.cross'), - // translate('::Public.services.mobile.features.push'), - // translate('::Public.services.mobile.features.store'), - // ], - // }, - // { - // icon: , - // title: translate('::Public.services.database.title'), - // description: translate('::Public.services.database.desc'), - // type: 'service', - // features: [ - // translate('::Public.services.database.features.design'), - // translate('::Public.services.database.features.optimization'), - // translate('::Public.services.database.features.migration'), - // translate('::Public.services.database.features.backup'), - // translate('::Public.services.database.features.recovery'), - // ], - // }, - // { - // icon: , - // title: translate('::Public.services.integration.title'), - // description: translate('::Public.services.integration.desc'), - // type: 'service', - // features: [ - // translate('::Public.services.integration.features.api'), - // translate('::Public.services.integration.features.middleware'), - // translate('::Public.services.integration.features.legacy'), - // translate('::Public.services.integration.features.realtime'), - // translate('::Public.services.integration.features.monitoring'), - // ], - // }, - // { - // icon: , - // title: translate('::Public.services.consulting.title'), - // description: translate('::Public.services.consulting.desc'), - // type: 'service', - // features: [ - // translate('::Public.services.consulting.features.tech'), - // translate('::Public.services.consulting.features.project'), - // translate('::Public.services.consulting.features.digital'), - // translate('::Public.services.consulting.features.risk'), - // translate('::Public.services.consulting.features.training'), - // ], - // }, - // { - // icon: , // Remote Branch Support - // title: translate('::Public.services.support.branchRemote.title'), - // description: '', - // type: 'support', - // features: [ - // translate('::Public.services.support.branchRemote.features.priority'), - // translate('::Public.services.support.branchRemote.features.remote'), - // translate('::Public.services.support.branchRemote.features.optimization'), - // translate('::Public.services.support.branchRemote.features.maintenance'), - // translate('::Public.services.support.branchRemote.features.consulting'), - // ], - // }, - // { - // icon: , // Backup Support - // title: translate('::Public.services.support.backup.title'), - // description: '', - // type: 'support', - // features: [ - // translate('::Public.services.support.backup.features.daily'), - // translate('::Public.services.support.backup.features.encrypted'), - // translate('::Public.services.support.backup.features.recovery'), - // translate('::Public.services.support.backup.features.verification'), - // translate('::Public.services.support.backup.features.access'), - // ], - // }, - // { - // icon: , // SMS Support - // title: translate('::Public.services.support.sms.title'), - // description: '', - // type: 'support', - // features: [ - // translate('::Public.services.support.sms.features.packages'), - // translate('::Public.services.support.sms.features.bulk'), - // translate('::Public.services.support.sms.features.template'), - // translate('::Public.services.support.sms.features.reporting'), - // translate('::Public.services.support.sms.features.api'), - // ], - // }, - // ] - - // Botları çek + const [loading, setLoading] = useState(true) const iconColors = [ 'text-blue-600', @@ -156,6 +35,8 @@ const Services: React.FC = () => { } useEffect(() => { + setLoading(true) + const fetchServices = async () => { try { const result = await getServices() @@ -170,6 +51,8 @@ const Services: React.FC = () => { setServices(items) } catch (error) { console.error('Service listesi alınırken hata oluştu:', error) + } finally { + setLoading(false) } }