sozsoft-platform/ui
2026-10-17 21:51:10 +03:00
..
.vscode Tailwind 4 geçişi 2026-10-17 00:13:31 +03:00
nginx Fable5 bakışı ile düzenlemeler 2026-10-17 21:51:10 +03:00
public Seo çalışmaları 2026-09-22 23:59:36 +03:00
src Fable5 bakışı ile düzenlemeler 2026-10-17 21:51:10 +03:00
.dockerignore Fable5 bakışı ile düzenlemeler 2026-10-17 21:51:10 +03:00
.env ReleaseNoteAppService güncellemesi 2026-08-13 00:26:36 +03:00
.env.dev Initial 2026-02-24 23:44:16 +03:00
.env.production Initial 2026-02-24 23:44:16 +03:00
.gitignore Migrations ve Seeds düzenlemesi 2026-08-12 15:20:00 +03:00
.oxlintrc.json Tailwind 4 geçişi 2026-10-17 00:13:31 +03:00
.prettierignore Initial 2026-02-24 23:44:16 +03:00
.prettierrc.json Initial 2026-02-24 23:44:16 +03:00
index.d.ts Initial 2026-02-24 23:44:16 +03:00
index.html Seo çalışmaları 2026-09-22 23:59:36 +03:00
package-lock.json Tailwind 4 geçişi 2026-10-17 00:13:31 +03:00
package.json Tailwind 4 geçişi 2026-10-17 00:13:31 +03:00
README.md Fable5 bakışı ile düzenlemeler 2026-10-17 21:51:10 +03:00
safelist.txt Tailwind 4 geçişi 2026-10-17 00:13:31 +03:00
Sozsoft.Platform.Ui.Dockerfile Fable5 bakışı ile düzenlemeler 2026-10-17 21:51:10 +03:00
tsconfig.json Kullanılmayan değişkenler hata olarak görünmüyor. 2026-07-10 12:27:30 +03:00
tsconfig.node.json Tailwind 4 geçişi 2026-10-17 00:13:31 +03:00
tsconfig.node.tsbuildinfo seeds klasörü konum değiştirildi 2026-10-05 15:58:59 +03:00
twSafelistGenerator.ts Tailwind 4 geçişi 2026-10-17 00:13:31 +03:00
vite.config.ts Fable5 bakışı ile düzenlemeler 2026-10-17 21:51:10 +03:00

Sözsoft Platform UI

React 19 + Vite 8 (rolldown) + DevExtreme + easy-peasy + Tailwind 4. Ekranların tamamı veritabanından gelen ListForm/menü/route tanımlarıyla çizilir; bu klasör yalnızca çekirdek motoru taşır. Platformun geneli için README.md, çalışma kuralları için CLAUDE.md.

Komutlar

Komut Ne yapar
npm start Dev sunucusu (3000). prestart DevExtreme lisans dosyasını üretir.
npm run build Üretim derlemesi → dist/. `--mode dev
npm run preview Yerel önizleme; üretim sunucusu değildir (bkz. Konteyner).
npm run typecheck / npm run lint tsc --noEmit / oxlint. Her değişiklikten sonra ikisi de 0 hata vermeli.
npm run format prettier + oxlint --fix.

Ortam değişkenleri .env (yerel), .env.dev, .env.production: VITE_API_URL, VITE_CDN_URL. Node/npm sürümü package.json → volta.

Derleme çıktısı

vite.config.ts üç şey üretir:

  • Chunk'lar assets/js/entry-*.js (giriş), assets/js/vendor-react-*.js, vendor-router-*.js (her açılışta yüklenen, sürümler arası değişmeyen çekirdek) ve rolldown'un otomatik böldüğü lazy chunk'lar; CSS assets/css/, görsel/font assets/img|fonts/. Adlar hash'lidir: aynı ad = aynı içerik.
  • sw.js — src/sw.ts'ten (vite-plugin-pwa, injectManifest). Precache manifest'i yalnızca kabuğu (PRECACHE_GLOBS) taşır.
  • warmup.json — warmupManifest() eklentisi: bütün JS/CSS chunk'larının adı/boyutu ve her views/** modülünün açılması için gereken dosya zinciri (routes). Service worker ısıtma ve eski sürüm temizliği için okur.

twSafelistGenerator.ts: custom component kodu veritabanında yaşadığı için Tailwind'in göremediği sınıflar safelist.txt'ten derlemeye eklenir.

Rota ve bileşen çözümü

src/routes/dynamicRouteLoader.ts import.meta.glob('../views/**/*.tsx') ile bütün view modüllerini lazy olarak kaydeder; App.Menus.Routes kaydındaki componentPath bu haritadan çözülür (hasPhysicalComponent), karşılığı olmayan rota router'a alınmaz. Runtime bileşenler (Custom Components) ComponentContext üzerinden çizilir ve aynı yolu paylaşan fiziksel rotayı gölgeler. Ayrıntı: README.md §18.1.

PWA ve sürüm geçişi

Kod: src/sw.ts (service worker), src/views/version/swRegistration.ts (kayıt/güncelleme motoru, entry chunk'ta, React'a bağımlı değil), AppUpdateDialog.tsx (lazy, kendi React kökünde mount edilir), installPrompt.ts (PWA kurulum teklifi).

  1. Kurulum = kabuk. install olayı yalnızca precache manifest'indeki dosyaları indirir (index.html, entry JS/CSS, vendor chunk'ları). Biri inmezse kurulum düşer; yarım sürümle aktive olunmaz. İndirme ilerlemesi dosya dosya istemciye yayınlanır.
  2. Isıtma = bileşenler. Oturum açılınca store, yetkili protected rotaların view modüllerini setWarmupRoutes ile bildirir; worker ENSURE_WARMUP ile warmup.json'daki zinciri arka planda indirir. Ziyaretçi ve giriş ekranı hiçbir bileşen indirmez. Best-effort: inemeyen dosya atlanır, süre bütçesi dolunca kalanlar runtime cache'e kalır.
  3. Güncelleme. Deploy'da herhangi bir chunk değişince entry hash'i, index.html ve sw.js manifest'i değişir; registration.update() (kayıt, sekmeye dönüş, bağlantı, 30 dk yoklama) yeni worker'ı görür → kurulum → SKIP_WAITING → controllerchange → yenileme. Pencere geçiş bitene kadar kapanmaz; ilk kurulumda (controller yok) kapatılabilir.
  4. Sürüm numarası ve notlar backend'dedir (extraProperties.changeLogs, /api/app/change-log); UI'da sürüm dosyası yoktur. Son görülen sürüm store'un locale diliminde tutulur, giriş sonrası changelog yönlendirmesi oradan okunur.
  5. Runtime cache. Hash'li dosyalar cache-first (sozsoft-assets-v1, aktivasyonda warmup.json'da olmayan eski chunk'lar silinir), DevExtreme temaları stale-while-revalidate, font/görsel cache-first. /api, /connect, /swagger, /signalr, /hangfire worker'a girmez. Gezinmeler kabuk cache'indeki index.html ile anında açılır, kopya arka planda tazelenir.

Deploy sonrası eksik / değişmiş chunk

İki katman birlikte çalışır:

  • Sunucu tarafı (asıl çözüm): konteyner nginx'i hash'li assets/ dosyalarını /srv/assets volume'undan servis eder; nginx/40-archive-assets.sh her açılışta yeni sürümün dosyalarını arşive ekler (var olanı ezmez), imajda artık olmayan ve 30 günden eski dosyaları siler. Açık sekmeler deploy'dan sonra da kendi sürümlerinin chunk'larını bulur.
  • İstemci tarafı (yedek): bir chunk yine de bulunamazsa Vite vite:preloadError fırlatır; handleChunkLoadFailures kabuk cache'ini silip sayfayı bir kez yeniler (reloadWithFreshShell; düz reload eski index.html'i geri getirirdi). Koruma bayrağı (localStorage.chunkReloadGuard, 10 s) döngüyü keser; ikinci başarısızlık hata sınırına düşer, Views.tsx isChunkLoadError ile "uygulama güncellendi, yenileyin" mesajı ve reloadApp butonu gösterir. Hata sınırı rota değişince kendini sıfırlar (resetKeys).

Dev'de service worker kapalıdır (VITE_PWA_DEV=true ile açılır); kapalıyken önceki kayıtlar temizlenir, yoksa sw.js yerine index.html döner ve "unsupported MIME type" hatası üretirdi.

Konteyner

Sozsoft.Platform.Ui.Dockerfile iki aşamalıdır:

  1. node:24.22-alpine — npm run build:production -- --mode $ENV; DevExtreme lisansı yalnızca bu aşamada, tek RUN içinde yazılıp silinir. .env.$ENV'deki VITE_API_URL /tmp/api-url olarak ikinci aşamaya geçer.
  2. nginx:1.27-alpine — dist/ + nginx/default.conf (${API_URL} envsubst ile build'de doldurulur, nginx -t ile doğrulanır) + /docker-entrypoint.d/40-archive-assets.sh.

nginx/default.conf sözleşmesi:

Yol Cache-Control Not
/assets/** public, max-age=31536000, immutable /srv/assets (imaj + arşiv)
/index.html, /sw.js, /warmup.json, /manifest.webmanifest no-store, no-cache, must-revalidate sürüm kapısı
/css/** (DevExtreme temaları) public, max-age=86400 hash'siz, SW tazeler
/img/** public, max-age=604800
/sitemap.xml, /robots.txt — API'ye proxy (SeoController)
diğer — SPA fallback → index.html

Compose (configs/deployment/docker-compose-app.yml): ui servisi 3002:80, volume ui-assets:/srv/assets. Deploy (scripts/deploy/app.sh) konteynerleri önceden durdurmaz: pull → up -d; kesinti yalnızca konteyner değişimi kadardır. Ters vekil nginx.conf Cache-Control başlıklarını ezmez; başlıklar konteynerden gelir, tenant alt alan adları da aynı başlıkları alır.

Kod düzeni

src/
  components/   paylaşılan bileşenler (ui/, shared/, template/, layouts/, list/form editörleri…)
  views/        rota bileşenleri (import.meta.glob ile lazy); views/shared/ hariç
  routes/       dinamik router ve route çözümü
  store/        easy-peasy modelleri (abpConfig, auth, locale, base…)
  services/     axios istemcileri (authApi, platformApi) ve SignalR servisleri
  proxy/        API DTO tipleri
  utils/        yardımcılar ve hook'lar
  sw.ts         service worker

Grid/Tree popup'ı ile form sayfası editör yeteneklerini views/list/shared/* ve form/editors/* üzerinden paylaşır; scriptRecipes.ts C# Domain.Shared/Editors/* ile birebir aynı çıktıyı üretmek zorundadır (bkz. CLAUDE.md "Cross-cutting"). Grid ve Tree'nin ortak iç seçenekleri (düzenleme, araç çubuğu, süzgeçler, hücre editörü şablonları) tek yerde çizilir: views/list/shared/listOptionChildren.tsx.

Ortak yardımcılar; aynı işin ikinci bir kopyası yazılmaz:

İş Yardımcı
Kullanıcıya görünen metin useLocalization().translate('::Anahtar'); React dışı kodda utils/translate.ts translateStatic
Toast bildirimi utils/notify.tsx notifySuccess/Info/Warning/Danger(başlık?, { message })
API hatasının bildirimi services/apiError.ts pushApiError
Kimlik üretimi utils/id.ts createId (UUID), createShortId
Tarih/sayı/para/boyut utils/localeFormat.ts (aşağıda)

Tarih ve sayı biçimi

Kullanıcıya görünen her tarih ve sayı aktif kültürle biçimlenir; kültür useLocale ile store'dan <html lang>'e yazılır ve tek giriş src/utils/localeFormat.ts'tir:

İhtiyaç Çağrı
Tarih / saat / tarih+saat formatLocaleDate, formatLocaleTime, formatLocaleDateTime (dayjs L, LT, L LT); uzun tarih formatLocaleLongDate (LL)
Sayı formatLocaleNumber(v, { minimumFractionDigits, maximumFractionDigits, useGrouping }) — varsayılan binlik ayracı yok (yıl, kimlik, adet), tutar için useGrouping: true
Sabit ondalık / dosya boyutu formatLocaleFixed(v, 2), formatLocaleBytes(bytes)
Para formatLocaleCurrency(v, currencyCode) — kod veriden gelir; verisi olmayan fiyat DEFAULT_CURRENCY (constants/app.constant.ts)
Ham endpoint değeri formatLocaleValue(v) (ISO tarih ve sayıyı çevirir, diğerini olduğu gibi bırakır)

Hepsi boş/geçersiz değer için '' döner ve Date | string | number | Dayjs alır. Tarih seçiciler (DatePicker, DateTimepicker) inputFormat verilmeyince kültürün desenini kullanır; prop yalnızca makine biçimi gerektiğinde verilir.

DevExtreme kolon/editör biçimleri adlı biçimdir: shortDate, shortDateShortTime, shortTime, decimal, fixedPoint, currency (EditorOptions.Date/DateTime/Time ↔ presets.ts). Kültür DevExtremeBoundary → configureDevExtreme ile verilir: locale(cultureName) tarih/sayıyı, loadMessages ile yüklenen devextreme/localization/messages/{dil}.json arayüz metinlerini (Evet/Hayır, "Veri yok", filtre satırı) çevirir; paket dışı dil İngilizce metinle kalır.

Yazılmayanlar: 'DD.MM.YYYY', 'HH:mm' gibi sabit dayjs desenleri, toLocaleString() / toLocaleDateString('tr-TR'), Intl.*Format('tr-TR'), görüntülenen ondalık için toFixed. Sunucu tarafı karşılığı CultureFormat + CultureInfo.CurrentCulture'dır.

Makine biçimi serbesttir ve localeFormat'tan geçmez: <input type="date|datetime-local"> değeri ve API yüküne giden ISO metin (YYYY-MM-DD, YYYY-MM-DDTHH:mm:ss), gruplama/sorgu anahtarı, DevExtreme dateSerializationFormat, yakınlaştırma yüzdesi gibi sayısal hesap yuvarlaması (Number(x.toFixed(2))), metin karşılaştırması için toLocaleLowerCase('tr-TR'). AppUpdateDialog uygulama ağacının dışında, kültür bilinmeden açıldığı için kendi formatBytes kopyasını taşır.