About ve Contact sayfaları dinamik şekilde değiştirildi.
This commit is contained in:
parent
6fdc2c3231
commit
1240c8dfb4
22 changed files with 1177 additions and 605 deletions
|
|
@ -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<Guid>
|
||||
{
|
||||
public Guid? TenantId { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string StatsJson { get; set; }
|
||||
public List<StatDto> StatsDto
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(StatsJson))
|
||||
return JsonSerializer.Deserialize<List<StatDto>>(StatsJson);
|
||||
return new List<StatDto>();
|
||||
}
|
||||
set { StatsJson = JsonSerializer.Serialize(value); }
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string DescriptionsJson { get; set; }
|
||||
public List<string> DescriptionsDto
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(DescriptionsJson))
|
||||
return JsonSerializer.Deserialize<List<string>>(DescriptionsJson);
|
||||
return [];
|
||||
}
|
||||
set { DescriptionsJson = JsonSerializer.Serialize(value); }
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string SectionsJson { get; set; }
|
||||
public List<SectionDto> SectionsDto
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(SectionsJson))
|
||||
return JsonSerializer.Deserialize<List<SectionDto>>(SectionsJson);
|
||||
return new List<SectionDto>();
|
||||
}
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Guid>
|
||||
{
|
||||
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<BankDto>(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<WorkHoursDto>(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<MapDto>(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; }
|
||||
}
|
||||
|
|
@ -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<PaymentMethod, string> _paymentMethodRepository;
|
||||
private readonly IRepository<InstallmentOption> _installmentOptionRepository;
|
||||
private readonly IRepository<Order, Guid> _orderRepository;
|
||||
private readonly IRepository<About, Guid> _aboutRepository;
|
||||
private readonly IRepository<Contact, Guid> _contactRepository;
|
||||
|
||||
public PublicAppService(
|
||||
IRepository<Service, Guid> serviceRepository,
|
||||
|
|
@ -41,7 +45,9 @@ public class PublicAppService : PlatformAppService
|
|||
IRepository<Product, Guid> productRepository,
|
||||
IRepository<PaymentMethod, string> paymentMethodRepository,
|
||||
IRepository<InstallmentOption> installmentOptionRepository,
|
||||
IRepository<Order, Guid> orderRepository
|
||||
IRepository<Order, Guid> orderRepository,
|
||||
IRepository<About, Guid> aboutRepository,
|
||||
IRepository<Contact, Guid> contactRepository
|
||||
)
|
||||
{
|
||||
_serviceRepository = serviceRepository;
|
||||
|
|
@ -54,6 +60,8 @@ public class PublicAppService : PlatformAppService
|
|||
_paymentMethodRepository = paymentMethodRepository;
|
||||
_installmentOptionRepository = installmentOptionRepository;
|
||||
_orderRepository = orderRepository;
|
||||
_aboutRepository = aboutRepository;
|
||||
_contactRepository = contactRepository;
|
||||
}
|
||||
|
||||
public async Task<List<ServiceDto>> GetServicesListAsync()
|
||||
|
|
@ -268,4 +276,17 @@ public class PublicAppService : PlatformAppService
|
|||
PaymentMethod = entity.PaymentMethod
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<AboutDto> GetAboutAsync()
|
||||
{
|
||||
var entity = await _aboutRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(About));
|
||||
return ObjectMapper.Map<About, AboutDto>(entity);
|
||||
}
|
||||
|
||||
public async Task<ContactDto> GetContactAsync()
|
||||
{
|
||||
var entity = await _contactRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(Contact));
|
||||
|
||||
return ObjectMapper.Map<Contact, ContactDto>(entity);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PaymentMethod, PaymentMethodDto>();
|
||||
CreateMap<InstallmentOption, InstallmentOptionDto>();
|
||||
CreateMap<Order, OrderDto>();
|
||||
CreateMap<About, AboutDto>();
|
||||
CreateMap<Contact, ContactDto>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,8 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency
|
|||
private readonly IRepository<CustomComponent, Guid> _customComponentRepository;
|
||||
private readonly IRepository<ReportCategory, Guid> _reportCategoriesRepository;
|
||||
private readonly IRepository<Service, Guid> _servicesRepository;
|
||||
private readonly IRepository<About, Guid> _aboutRepository;
|
||||
private readonly IRepository<Contact, Guid> _contactRepository;
|
||||
|
||||
public PlatformDataSeeder(
|
||||
IRepository<Language, Guid> languages,
|
||||
|
|
@ -104,7 +106,9 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency
|
|||
IRepository<InstallmentOption, Guid> InstallmentOptionRepository,
|
||||
IRepository<CustomComponent, Guid> CustomComponentRepository,
|
||||
IRepository<ReportCategory, Guid> ReportCategoriesRepository,
|
||||
IRepository<Service, Guid> ServicesRepository
|
||||
IRepository<Service, Guid> ServicesRepository,
|
||||
IRepository<About, Guid> AboutRepository,
|
||||
IRepository<Contact, Guid> 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<CountryGroup>()
|
||||
.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<CountryGroup>(capacity: 1000);
|
||||
var seenCodes = new HashSet<string>(); // JSON içindeki duplicate’leri yakalamak için
|
||||
|
||||
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<CountryGroupDto>(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<CountryGroup> 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<Country>()
|
||||
.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<Country>(capacity: 1000);
|
||||
var seenCodes = new HashSet<string>(); // JSON içindeki duplicate’leri yakalamak için
|
||||
|
||||
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<CountryDto>(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<Country> 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<City>()
|
||||
.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<City>(capacity: 5000);
|
||||
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<CityDto>(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<City> 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<District>()
|
||||
.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<District>(capacity: 5000);
|
||||
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<DistrictDto>(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<District> 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<SeederDto>();
|
||||
|
||||
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<CountryGroup>()
|
||||
.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<CountryGroup>(capacity: 1000);
|
||||
var seenCodes = new HashSet<string>(); // JSON içindeki duplicate’leri yakalamak için
|
||||
|
||||
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<CountryGroupDto>(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<CountryGroup> 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<Country>()
|
||||
.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<Country>(capacity: 1000);
|
||||
var seenCodes = new HashSet<string>(); // JSON içindeki duplicate’leri yakalamak için
|
||||
|
||||
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<CountryDto>(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<Country> 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<City>()
|
||||
.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<City>(capacity: 5000);
|
||||
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<CityDto>(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<City> 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<District>()
|
||||
.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<District>(capacity: 5000);
|
||||
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<DistrictDto>(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<District> entities)
|
||||
{
|
||||
var dbCtx = await _districtRepository.GetDbContextAsync();
|
||||
await dbCtx.BulkInsertAsync(entities, new BulkConfig
|
||||
{
|
||||
PreserveInsertOrder = true,
|
||||
SetOutputIdentity = true,
|
||||
BatchSize = 5000
|
||||
});
|
||||
await SeedDistrictsAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<CustomComponentSeedDto> CustomComponents { get; set; }
|
||||
public List<ReportCategorySeedDto> ReportCategories { get; set; }
|
||||
public List<ServiceSeedDto> Services { get; set; }
|
||||
public List<AboutSeedDto> Abouts { get; set; }
|
||||
public List<ContactSeedDto> 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<StatDto> Stats { get; set; }
|
||||
public List<string> Descriptions { get; set; }
|
||||
public List<SectionDto> 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; }
|
||||
}
|
||||
28
api/src/Kurs.Platform.Domain/Entities/About.cs
Normal file
28
api/src/Kurs.Platform.Domain/Entities/About.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using Volo.Abp.Domain.Entities.Auditing;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
|
||||
namespace Kurs.Platform.Entities;
|
||||
|
||||
public class About : FullAuditedEntity<Guid>, 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;
|
||||
}
|
||||
}
|
||||
45
api/src/Kurs.Platform.Domain/Entities/Contact.cs
Normal file
45
api/src/Kurs.Platform.Domain/Entities/Contact.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using Volo.Abp.Domain.Entities.Auditing;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
|
||||
namespace Kurs.Platform.Entities;
|
||||
|
||||
public class Contact : FullAuditedEntity<Guid>, 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -80,6 +80,9 @@ public class PlatformDbContext :
|
|||
public DbSet<PaymentMethod> PaymentMethods { get; set; }
|
||||
public DbSet<InstallmentOption> InstallmentOptions { get; set; }
|
||||
|
||||
public DbSet<About> Abouts { get; set; }
|
||||
public DbSet<Contact> Contacts { get; set; }
|
||||
|
||||
public DbSet<Order> Orders { get; set; }
|
||||
public DbSet<OrderItem> OrderItems { get; set; }
|
||||
|
||||
|
|
@ -832,5 +835,33 @@ public class PlatformDbContext :
|
|||
)
|
||||
);
|
||||
});
|
||||
|
||||
builder.Entity<About>(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<Contact>(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)");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
|||
namespace Kurs.Platform.Migrations
|
||||
{
|
||||
[DbContext(typeof(PlatformDbContext))]
|
||||
[Migration("20250820190627_GenelDuzenleme")]
|
||||
[Migration("20250821081019_GenelDuzenleme")]
|
||||
partial class GenelDuzenleme
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
|
@ -650,6 +650,59 @@ namespace Kurs.Platform.Migrations
|
|||
b.ToTable("PNotificationRule", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.About", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("CreationTime");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("CreatorId");
|
||||
|
||||
b.Property<Guid?>("DeleterId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("DeleterId");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("DescriptionsJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("IsDeleted");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("LastModificationTime");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<string>("SectionsJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("StatsJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid?>("TenantId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("TenantId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PAbout", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.AiBot", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
|
|
@ -1506,6 +1559,79 @@ namespace Kurs.Platform.Migrations
|
|||
b.ToTable("PCity", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.Contact", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Address")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("BankJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("CreationTime");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("CreatorId");
|
||||
|
||||
b.Property<Guid?>("DeleterId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("DeleterId");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(150)
|
||||
.HasColumnType("nvarchar(150)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("IsDeleted");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("LastModificationTime");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<string>("Location")
|
||||
.HasMaxLength(150)
|
||||
.HasColumnType("nvarchar(150)");
|
||||
|
||||
b.Property<string>("MapJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Phone")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("TaxNumber")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<Guid?>("TenantId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("TenantId");
|
||||
|
||||
b.Property<string>("WorkHoursJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PContact", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.Country", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
|
|
@ -173,6 +173,55 @@ namespace Kurs.Platform.Migrations
|
|||
table: "PCity",
|
||||
columns: new[] { "CountryCode", "Code" });
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PAbout",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
StatsJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
DescriptionsJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
SectionsJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PAbout", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PContact",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
Address = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
Phone = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
|
||||
Email = table.Column<string>(type: "nvarchar(150)", maxLength: 150, nullable: true),
|
||||
Location = table.Column<string>(type: "nvarchar(150)", maxLength: 150, nullable: true),
|
||||
TaxNumber = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
|
||||
BankJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
WorkHoursJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
MapJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DeletionTime = table.Column<DateTime>(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");
|
||||
|
||||
|
|
@ -647,6 +647,59 @@ namespace Kurs.Platform.Migrations
|
|||
b.ToTable("PNotificationRule", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.About", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("CreationTime");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("CreatorId");
|
||||
|
||||
b.Property<Guid?>("DeleterId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("DeleterId");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("DescriptionsJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("IsDeleted");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("LastModificationTime");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<string>("SectionsJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("StatsJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid?>("TenantId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("TenantId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PAbout", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.AiBot", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
|
|
@ -1503,6 +1556,79 @@ namespace Kurs.Platform.Migrations
|
|||
b.ToTable("PCity", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.Contact", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Address")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("BankJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("CreationTime");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("CreatorId");
|
||||
|
||||
b.Property<Guid?>("DeleterId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("DeleterId");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(150)
|
||||
.HasColumnType("nvarchar(150)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("IsDeleted");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("LastModificationTime");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<string>("Location")
|
||||
.HasMaxLength(150)
|
||||
.HasColumnType("nvarchar(150)");
|
||||
|
||||
b.Property<string>("MapJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Phone")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("TaxNumber")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<Guid?>("TenantId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("TenantId");
|
||||
|
||||
b.Property<string>("WorkHoursJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PContact", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.Country", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
|
|
|
|||
|
|
@ -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"), {
|
||||
|
|
|
|||
22
ui/src/proxy/about/models.ts
Normal file
22
ui/src/proxy/about/models.ts
Normal file
|
|
@ -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
|
||||
}
|
||||
34
ui/src/proxy/contact/models.ts
Normal file
34
ui/src/proxy/contact/models.ts
Normal file
|
|
@ -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
|
||||
}
|
||||
12
ui/src/services/about.ts
Normal file
12
ui/src/services/about.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { AboutDto } from '@/proxy/about/models'
|
||||
import apiService from './api.service'
|
||||
|
||||
export function getAbout() {
|
||||
return apiService.fetchData<AboutDto>(
|
||||
{
|
||||
method: 'GET',
|
||||
url: '/api/app/public/about',
|
||||
},
|
||||
{ apiName: 'Default' },
|
||||
)
|
||||
}
|
||||
12
ui/src/services/contact.ts
Normal file
12
ui/src/services/contact.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import apiService from './api.service'
|
||||
import { ContactDto } from '@/proxy/contact/models'
|
||||
|
||||
export function getContact() {
|
||||
return apiService.fetchData<ContactDto>(
|
||||
{
|
||||
method: 'GET',
|
||||
url: '/api/app/public/contact',
|
||||
},
|
||||
{ apiName: 'Default' },
|
||||
)
|
||||
}
|
||||
|
|
@ -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<HTMLDivElement>(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 }
|
||||
}
|
||||
|
|
@ -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<AboutDto>()
|
||||
|
||||
// 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 = () => {
|
|||
<div className="py-10 bg-white">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
|
||||
{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 (
|
||||
<div key={index} className="text-center" ref={elementRef}>
|
||||
<IconComponent className="w-12 h-12 text-blue-600 mx-auto mb-4" />
|
||||
<div className="text-4xl font-bold text-gray-900 mb-2">
|
||||
{displayValue}
|
||||
</div>
|
||||
<div className="text-gray-600">{translate(stat.labelKey)}</div>
|
||||
<div key={index} className="text-center">
|
||||
{IconComponent && (
|
||||
<IconComponent className={`w-12 h-12 mx-auto mb-4 ${getRandomColor()}`} />
|
||||
)}
|
||||
<div className="text-4xl font-bold text-gray-900 mb-2">{displayValue}</div>
|
||||
<div className="text-gray-600">{translate('::' + stat.labelKey)}</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
|
@ -174,28 +97,24 @@ const About: React.FC = () => {
|
|||
<div className="container mx-auto px-4">
|
||||
<div className="mb-6">
|
||||
<div className="p-5 mx-auto mx-auto text-gray-800 text-lg leading-relaxed shadow-md bg-white border-l-4 border-blue-600">
|
||||
<p>
|
||||
{translate(aboutPageData.descriptions[0])}
|
||||
</p>
|
||||
<p>{translate('::' + about?.descriptionsDto[0])}</p>
|
||||
<p className="text-center p-5 text-blue-800">
|
||||
{translate(aboutPageData.descriptions[1])}
|
||||
</p>
|
||||
<p>
|
||||
{translate(aboutPageData.descriptions[2])}
|
||||
{translate('::' + about?.descriptionsDto[1])}
|
||||
</p>
|
||||
<p>{translate('::' + about?.descriptionsDto[2])}</p>
|
||||
<p className="text-center p-5 text-blue-800">
|
||||
{translate(aboutPageData.descriptions[3])}
|
||||
{translate('::' + about?.descriptionsDto[3])}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
|
||||
{aboutPageData.sections.map((section, index) => (
|
||||
{about?.sectionsDto.map((section, index) => (
|
||||
<div key={index} className="bg-white p-8 rounded-xl shadow-lg">
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-4">
|
||||
{translate(section.key)}
|
||||
{translate('::' + section.key)}
|
||||
</h3>
|
||||
<p className="text-gray-700">{translate(section.descKey)}</p>
|
||||
<p className="text-gray-700">{translate('::' + section.descKey)}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<ContactDto>()
|
||||
|
||||
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 (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
|
|
@ -42,51 +64,51 @@ const Contact: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Section */}
|
||||
<div className="py-16">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
|
||||
{/* Contact Information */}
|
||||
<div className="space-y-4">
|
||||
<div className="bg-white rounded-xl shadow-lg p-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
{translate('::Public.contact.info.title')}
|
||||
</h2>
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-start space-x-4">
|
||||
<FaMapPin className="w-6 h-6 text-blue-600 flex-shrink-0 mt-1" />
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start space-x-2">
|
||||
<FaMapPin className="w-5 h-5 text-blue-600 flex-shrink-0 mt-1" />
|
||||
<div>
|
||||
<p className="text-gray-600">{translate('::Public.contact.address.full')}</p>
|
||||
<p className="text-gray-600">{translate('::' + contact?.address)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start space-x-4">
|
||||
<FaPhone className="w-6 h-6 text-blue-600 flex-shrink-0" />
|
||||
<div className="flex items-start space-x-2">
|
||||
<FaPhone className="w-5 h-5 text-blue-600 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="text-gray-600">+90 (544) 769 7 638</p>
|
||||
<p className="text-gray-600">{contact?.phone}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start space-x-4">
|
||||
<FaMailBulk className="w-6 h-6 text-blue-600 flex-shrink-0" />
|
||||
<div className="flex items-start space-x-2">
|
||||
<FaMailBulk className="w-5 h-5 text-blue-600 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="text-gray-600">
|
||||
<a
|
||||
href="mailto:destek@sozsoft.com"
|
||||
href={`mailto:${contact?.email}`}
|
||||
className="hover:underline text-blue-600"
|
||||
>
|
||||
destek@sozsoft.com
|
||||
{contact?.email}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start space-x-4">
|
||||
<FaBuilding className="w-6 h-6 text-blue-600 flex-shrink-0" />
|
||||
<div className="flex items-start space-x-2">
|
||||
<FaBuilding className="w-5 h-5 text-blue-600 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="text-gray-600">Kozyatağı</p>
|
||||
<p className="text-gray-600">{contact?.location}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start space-x-4">
|
||||
<FaFileAlt className="w-6 h-6 text-blue-600 flex-shrink-0" />
|
||||
<div className="flex items-start space-x-2">
|
||||
<FaFileAlt className="w-5 h-5 text-blue-600 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="text-gray-600">32374982750</p>
|
||||
<p className="text-gray-600">{contact?.taxNumber}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -96,21 +118,19 @@ const Contact: React.FC = () => {
|
|||
<h2 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
{translate('::Public.contact.bank.title')}
|
||||
</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="mb-2">
|
||||
<img
|
||||
src="/img/enpara.svg"
|
||||
alt="Enpara Logo"
|
||||
className="w-24 object-contain mt-1 flex-shrink-0"
|
||||
/>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900">Özlem Öztürk</h3>
|
||||
<p className="text-gray-600">
|
||||
03663 / Enpara
|
||||
<br />
|
||||
73941177
|
||||
<br />
|
||||
TR65 0011 1000 0000 0073 9411 77
|
||||
</p>
|
||||
<h3 className="font-semibold text-gray-900 mb-1">
|
||||
{contact?.bankDto.accountHolder}
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-1 ml-1">{contact?.bankDto.branch}</p>
|
||||
<p className="text-gray-600 mb-1 ml-1">{contact?.bankDto.accountNumber}</p>
|
||||
<p className="text-gray-600 mb-1 ml-1">{contact?.bankDto.iban}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -120,28 +140,24 @@ const Contact: React.FC = () => {
|
|||
<h2 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
{translate('::Public.contact.workHours')}
|
||||
</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start space-x-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<FaCalendarAlt className="w-5 h-5 text-blue-500" />
|
||||
<p className="text-gray-600">
|
||||
{translate('::Public.contact.workHours.weekday')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<FaCalendarCheck className="w-5 h-5 text-blue-500" />
|
||||
<p className="text-gray-600">
|
||||
{translate('::Public.contact.workHours.weekend')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<FaRegComment className="w-5 h-5 text-green-500" />
|
||||
<p className="text-gray-600">
|
||||
{translate('::Public.contact.workHours.whatsapp')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<FaCalendarAlt className="w-5 h-5 text-blue-500" />
|
||||
<p className="text-gray-600">
|
||||
{translate('::' + contact?.workHoursDto.weekday)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<FaCalendarCheck className="w-5 h-5 text-blue-500" />
|
||||
<p className="text-gray-600">
|
||||
{translate('::' + contact?.workHoursDto.weekend)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<FaRegComment className="w-5 h-5 text-green-500" />
|
||||
<p className="text-gray-600">
|
||||
{translate('::' + contact?.workHoursDto.whatsapp)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -150,17 +166,19 @@ const Contact: React.FC = () => {
|
|||
{/* Map Section */}
|
||||
<div className="bg-white rounded-xl shadow-lg p-2">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-8 text-center">
|
||||
{translate('::Public.contact.location')}
|
||||
{translate('::' + contact?.mapDto.title)}
|
||||
</h2>
|
||||
<div className="aspect-w-16 aspect-h-9 bg-gray-200 rounded-xl overflow-hidden">
|
||||
<iframe
|
||||
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="740"
|
||||
src={contact?.mapDto.src}
|
||||
width={contact?.mapDto.width}
|
||||
style={{ border: 0 }}
|
||||
allowFullScreen
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
height={contact?.mapDto.height}
|
||||
allowFullScreen={contact?.mapDto.allowFullScreen}
|
||||
loading={contact?.mapDto.loading as 'lazy' | 'eager' | undefined}
|
||||
referrerPolicy={
|
||||
contact?.mapDto.referrerPolicy as React.HTMLAttributeReferrerPolicy | undefined
|
||||
}
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -19,128 +19,7 @@ import navigationIcon from '@/configs/navigation-icon.config'
|
|||
const Services: React.FC = () => {
|
||||
const { translate } = useLocalization()
|
||||
const [services, setServices] = useState<ServiceDto[]>([])
|
||||
|
||||
// const services: ServiceDto[] = [
|
||||
// {
|
||||
// icon: <FaCode className="w-12 h-12 text-blue-600" />,
|
||||
// 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: <FaUsers className="w-12 h-12 text-purple-600" />,
|
||||
// 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: <FaShieldAlt className="w-12 h-12 text-green-600" />,
|
||||
// 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: <FaServer className="w-12 h-12 text-red-600" />,
|
||||
// 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: <FaGlobe className="w-12 h-12 text-yellow-600" />,
|
||||
// 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: <FaCog className="w-12 h-12 text-indigo-600" />,
|
||||
// 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: <FaUsers className="w-12 h-12 text-pink-600" />, // 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: <FaServer className="w-12 h-12 text-orange-600" />, // 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: <FaGlobe className="w-12 h-12 text-cyan-600" />, // 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue