Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d924ae0c30 |
@@ -14,6 +14,9 @@ import { ExpenseList } from './expenses/expense-list/expense-list';
|
||||
import { Profile } from './profile/profile';
|
||||
import { Settings } from './settings/settings';
|
||||
import { Calendar } from './calendar/calendar';
|
||||
import { Salarium } from './salary/salarium/salarium';
|
||||
import { SalaryAnalyse } from './salary/analyse/analyse';
|
||||
import { SalaryEntwicklung } from './salary/entwicklung/entwicklung';
|
||||
export const routes: Routes = [
|
||||
{ path: 'login', component: Login },
|
||||
{ path: 'register', component: Register },
|
||||
@@ -34,6 +37,9 @@ export const routes: Routes = [
|
||||
{ path: 'profile', component: Profile },
|
||||
{ path: 'settings', component: Settings },
|
||||
{ path: 'calendar', component: Calendar },
|
||||
{ path: 'salarium', component: Salarium },
|
||||
{ path: 'lohn-analyse', component: SalaryAnalyse },
|
||||
{ path: 'lohn-entwicklung', component: SalaryEntwicklung },
|
||||
],
|
||||
},
|
||||
{ path: '**', redirectTo: 'dashboard' },
|
||||
|
||||
@@ -157,6 +157,64 @@
|
||||
}
|
||||
</li>
|
||||
|
||||
<!-- Lohn -->
|
||||
<li class="relative">
|
||||
@if (sidebarService.collapsed()) {
|
||||
<!-- Collapsed: icon button opens flyout -->
|
||||
<button (click)="sidebarService.toggleFlyout('salary')"
|
||||
[class]="sidebarService.openFlyout() === 'salary' ? 'bg-gray-100 dark:bg-gray-700' : ''"
|
||||
class="relative flex items-center justify-center p-2 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
||||
<svg class="w-6 h-6 flex-shrink-0 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M4 19v2c0 .5523.44772 1 1 1h14c.5523 0 1-.4477 1-1v-2H4Z"/>
|
||||
<path fill="currentColor" fill-rule="evenodd" d="M9 3c0-.55228.44772-1 1-1h8c.5523 0 1 .44772 1 1v3c0 .55228-.4477 1-1 1h-2v1h2c.5096 0 .9376.38314.9939.88957L19.8951 17H4.10498l.90116-8.11043C5.06241 8.38314 5.49047 8 6.00002 8H12V7h-2c-.55228 0-1-.44772-1-1V3Zm1.01 8H8.00002v2.01H10.01V11Zm.99 0h2.01v2.01H11V11Zm5.01 0H14v2.01h2.01V11Zm-8.00998 3H10.01v2.01H8.00002V14ZM13.01 14H11v2.01h2.01V14Zm.99 0h2.01v2.01H14V14ZM11 4h6v1h-6V4Z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
@if (sidebarService.openFlyout() !== 'salary') {
|
||||
<span class="pointer-events-none absolute left-full ml-3 top-1/2 -translate-y-1/2 px-2 py-1 text-xs font-medium text-white bg-gray-900 dark:bg-gray-700 rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity duration-150 z-50">
|
||||
{{ 'sidebar.salary' | translate }}
|
||||
</span>
|
||||
}
|
||||
</button>
|
||||
<!-- Flyout -->
|
||||
@if (sidebarService.openFlyout() === 'salary') {
|
||||
<div class="absolute left-full top-0 ml-2 z-50 w-44 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg py-1">
|
||||
<p class="px-3 py-2 text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-wider">{{ 'sidebar.salary' | translate }}</p>
|
||||
<a routerLink="/salarium" (click)="sidebarService.closeFlyout(); sidebarService.closeMobile()"
|
||||
class="flex items-center px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md mx-1">
|
||||
{{ 'sidebar.salarium' | translate }}
|
||||
</a>
|
||||
<a routerLink="/lohn-analyse" (click)="sidebarService.closeFlyout(); sidebarService.closeMobile()"
|
||||
class="flex items-center px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md mx-1">
|
||||
{{ 'sidebar.salary_analyse' | translate }}
|
||||
</a>
|
||||
<a routerLink="/lohn-entwicklung" (click)="sidebarService.closeFlyout(); sidebarService.closeMobile()"
|
||||
class="flex items-center px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md mx-1">
|
||||
{{ 'sidebar.salary_entwicklung' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
} @else {
|
||||
<!-- Expanded: Angular-controlled dropdown -->
|
||||
<button type="button" (click)="sidebarService.toggleSalary()"
|
||||
class="flex items-center p-2 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
||||
<svg class="w-6 h-6 flex-shrink-0 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M4 19v2c0 .5523.44772 1 1 1h14c.5523 0 1-.4477 1-1v-2H4Z"/>
|
||||
<path fill="currentColor" fill-rule="evenodd" d="M9 3c0-.55228.44772-1 1-1h8c.5523 0 1 .44772 1 1v3c0 .55228-.4477 1-1 1h-2v1h2c.5096 0 .9376.38314.9939.88957L19.8951 17H4.10498l.90116-8.11043C5.06241 8.38314 5.49047 8 6.00002 8H12V7h-2c-.55228 0-1-.44772-1-1V3Zm1.01 8H8.00002v2.01H10.01V11Zm.99 0h2.01v2.01H11V11Zm5.01 0H14v2.01h2.01V11Zm-8.00998 3H10.01v2.01H8.00002V14ZM13.01 14H11v2.01h2.01V14Zm.99 0h2.01v2.01H14V14ZM11 4h6v1h-6V4Z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<span class="flex-1 ml-3 text-left whitespace-nowrap">{{ 'sidebar.salary' | translate }}</span>
|
||||
<svg [class.rotate-180]="sidebarService.salaryOpen()" class="w-4 h-4 transition-transform duration-200" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</button>
|
||||
@if (sidebarService.salaryOpen()) {
|
||||
<ul class="py-2 space-y-2">
|
||||
<li><a routerLink="/salarium" routerLinkActive="!bg-violet-50 !text-violet-700 dark:!bg-violet-900/20 dark:!text-violet-300" (click)="sidebarService.closeMobile()" class="flex items-center p-2 pl-11 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">{{ 'sidebar.salarium' | translate }}</a></li>
|
||||
<li><a routerLink="/lohn-analyse" routerLinkActive="!bg-violet-50 !text-violet-700 dark:!bg-violet-900/20 dark:!text-violet-300" (click)="sidebarService.closeMobile()" class="flex items-center p-2 pl-11 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">{{ 'sidebar.salary_analyse' | translate }}</a></li>
|
||||
<li><a routerLink="/lohn-entwicklung" routerLinkActive="!bg-violet-50 !text-violet-700 dark:!bg-violet-900/20 dark:!text-violet-300" (click)="sidebarService.closeMobile()" class="flex items-center p-2 pl-11 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">{{ 'sidebar.salary_entwicklung' | translate }}</a></li>
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<!-- Mobile: Notifications, Theme, Profile, Logout (hidden on desktop — those are in the navbar) -->
|
||||
|
||||
@@ -0,0 +1,434 @@
|
||||
<div class="p-4 sm:p-6 lg:p-8">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{{ 'lohn_analyse.title' | translate }}</h1>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ 'lohn_analyse.subtitle' | translate }}</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-[360px_1fr] gap-6 items-start">
|
||||
|
||||
<!-- ── Eingaben ────────────────────────────────────────────────────────── -->
|
||||
<div class="space-y-4">
|
||||
|
||||
<!-- Grundlagen -->
|
||||
<div class="rounded-lg bg-white border border-gray-200 dark:bg-gray-800 dark:border-gray-700 p-5">
|
||||
<h2 class="text-base font-semibold text-gray-900 dark:text-white mb-4">
|
||||
{{ 'lohn_analyse.section_basics' | translate }}
|
||||
</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
|
||||
<!-- Monatslohn -->
|
||||
<div>
|
||||
<label class="block mb-1.5 text-xs font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ 'lohn_analyse.label_monatslohn' | translate }}
|
||||
</label>
|
||||
<div class="relative">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-sm text-gray-500 dark:text-gray-400 pointer-events-none">CHF</span>
|
||||
<input type="number" min="0" step="100"
|
||||
[ngModel]="monatslohn()"
|
||||
(ngModelChange)="setMonatslohn($event)"
|
||||
class="block w-full rounded-lg border border-gray-300 bg-gray-50 pl-12 pr-3 py-2.5 text-sm text-gray-900 placeholder-gray-400
|
||||
focus:border-violet-600 focus:outline-none focus:ring-2 focus:ring-violet-300
|
||||
dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:focus:border-violet-500 dark:focus:ring-violet-500" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Anzahl Monate -->
|
||||
<div>
|
||||
<label class="block mb-1.5 text-xs font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ 'lohn_analyse.label_monate' | translate }}
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<button (click)="setAnzahlMonate('12')"
|
||||
[class]="anzahlMonate() === 12
|
||||
? 'flex-1 rounded-lg bg-violet-700 px-3 py-2 text-sm font-medium text-white dark:bg-violet-600'
|
||||
: 'flex-1 rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600'">
|
||||
12 {{ 'lohn_analyse.monate' | translate }}
|
||||
</button>
|
||||
<button (click)="setAnzahlMonate('13')"
|
||||
[class]="anzahlMonate() === 13
|
||||
? 'flex-1 rounded-lg bg-violet-700 px-3 py-2 text-sm font-medium text-white dark:bg-violet-600'
|
||||
: 'flex-1 rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600'">
|
||||
13 {{ 'lohn_analyse.monate' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kanton -->
|
||||
<div>
|
||||
<label class="block mb-1.5 text-xs font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ 'lohn_analyse.label_kanton' | translate }}
|
||||
</label>
|
||||
<div class="relative">
|
||||
<select [ngModel]="kanton()" (ngModelChange)="setKanton($event)"
|
||||
class="block w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2.5 text-sm text-gray-900
|
||||
focus:border-violet-600 focus:outline-none focus:ring-2 focus:ring-violet-300
|
||||
dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:focus:border-violet-500 dark:focus:ring-violet-500 appearance-none pr-10">
|
||||
@for (c of cantons; track c) {
|
||||
<option [value]="c">{{ ('canton_names.' + c) | translate }} ({{ c }})</option>
|
||||
}
|
||||
</select>
|
||||
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 9-7 7-7-7"/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ 'lohn_analyse.fak_rate_hint' | translate }} <span class="font-medium text-violet-700 dark:text-violet-400">{{ pct(result().fakRate) }}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Abrechnungsverfahren -->
|
||||
<div>
|
||||
<label class="block mb-1.5 text-xs font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ 'lohn_analyse.label_verfahren' | translate }}
|
||||
</label>
|
||||
<select [ngModel]="verfahren()" (ngModelChange)="setVerfahren($event)"
|
||||
class="block w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2.5 text-sm text-gray-900
|
||||
focus:border-violet-600 focus:outline-none focus:ring-2 focus:ring-violet-300
|
||||
dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:focus:border-violet-500 dark:focus:ring-violet-500 appearance-none">
|
||||
<option value="ordentlich">{{ 'lohn_analyse.verfahren_ordentlich' | translate }}</option>
|
||||
<option value="vereinfacht">{{ 'lohn_analyse.verfahren_vereinfacht' | translate }}</option>
|
||||
<option value="quellensteuer">{{ 'lohn_analyse.verfahren_quellensteuer' | translate }}</option>
|
||||
</select>
|
||||
@if (verfahren() !== 'ordentlich') {
|
||||
<p class="mt-1.5 flex items-center gap-1.5 text-xs text-amber-600 dark:text-amber-400">
|
||||
<svg class="w-3.5 h-3.5 flex-shrink-0" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v4m0 4h.01M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z"/>
|
||||
</svg>
|
||||
{{ 'lohn_analyse.verfahren_hint' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Optionale Sätze -->
|
||||
<div class="rounded-lg bg-white border border-gray-200 dark:bg-gray-800 dark:border-gray-700">
|
||||
<button (click)="showOptional.set(!showOptional())"
|
||||
class="flex w-full items-center justify-between px-5 py-3.5 text-sm font-medium text-gray-900 dark:text-white">
|
||||
<span>{{ 'lohn_analyse.section_optional' | translate }}</span>
|
||||
<svg [class.rotate-180]="showOptional()" class="w-4 h-4 text-gray-400 transition-transform duration-200" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 9-7 7-7-7"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
@if (showOptional()) {
|
||||
<div class="border-t border-gray-100 dark:border-gray-700 px-5 pb-5 pt-4 space-y-4">
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">{{ 'lohn_analyse.optional_hint' | translate }}</p>
|
||||
|
||||
<!-- AG -->
|
||||
<p class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
{{ 'lohn_analyse.beitraege_ag' | translate }}
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block mb-1.5 text-xs font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ 'lohn_analyse.ktv' | translate }} (%)
|
||||
</label>
|
||||
<input type="number" min="0" max="10" step="0.05"
|
||||
[ngModel]="agKtvPct()"
|
||||
(ngModelChange)="setAgKtvPct($event)"
|
||||
placeholder="0.00"
|
||||
class="block w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2.5 text-sm text-gray-900
|
||||
focus:border-violet-600 focus:outline-none focus:ring-2 focus:ring-violet-300
|
||||
dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:focus:border-violet-500 dark:focus:ring-violet-500" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block mb-1.5 text-xs font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ 'lohn_analyse.bu' | translate }} (%)
|
||||
</label>
|
||||
<input type="number" min="0" max="10" step="0.05"
|
||||
[ngModel]="agBuPct()"
|
||||
(ngModelChange)="setAgBuPct($event)"
|
||||
placeholder="0.00"
|
||||
class="block w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2.5 text-sm text-gray-900
|
||||
focus:border-violet-600 focus:outline-none focus:ring-2 focus:ring-violet-300
|
||||
dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:focus:border-violet-500 dark:focus:ring-violet-500" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AN -->
|
||||
<p class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
{{ 'lohn_analyse.abzuege_an' | translate }}
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block mb-1.5 text-xs font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ 'lohn_analyse.ktv' | translate }} (%)
|
||||
</label>
|
||||
<input type="number" min="0" max="10" step="0.05"
|
||||
[ngModel]="anKtvPct()"
|
||||
(ngModelChange)="setAnKtvPct($event)"
|
||||
placeholder="0.00"
|
||||
class="block w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2.5 text-sm text-gray-900
|
||||
focus:border-violet-600 focus:outline-none focus:ring-2 focus:ring-violet-300
|
||||
dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:focus:border-violet-500 dark:focus:ring-violet-500" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block mb-1.5 text-xs font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ 'lohn_analyse.nbu' | translate }} (%)
|
||||
</label>
|
||||
<input type="number" min="0" max="10" step="0.05"
|
||||
[ngModel]="anNbuPct()"
|
||||
(ngModelChange)="setAnNbuPct($event)"
|
||||
placeholder="0.00"
|
||||
class="block w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2.5 text-sm text-gray-900
|
||||
focus:border-violet-600 focus:outline-none focus:ring-2 focus:ring-violet-300
|
||||
dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:focus:border-violet-500 dark:focus:ring-violet-500" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div><!-- end inputs -->
|
||||
|
||||
|
||||
<!-- ── Ergebnisse ─────────────────────────────────────────────────────── -->
|
||||
<div class="space-y-4">
|
||||
|
||||
<!-- KPI-Zusammenfassung -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
|
||||
<!-- Bruttolohn -->
|
||||
<div class="rounded-lg bg-white border border-gray-200 dark:bg-gray-800 dark:border-gray-700 p-4">
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">
|
||||
{{ 'lohn_analyse.bruttolohn' | translate }}
|
||||
</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">
|
||||
CHF {{ result().grossMonthly | number:'1.2-2' }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500 mt-0.5">
|
||||
CHF {{ result().grossAnnual | number:'1.2-2' }} / {{ 'lohn_analyse.jahr' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Totalaufwand AG -->
|
||||
<div class="rounded-lg bg-amber-50 border border-amber-200 dark:bg-amber-900/20 dark:border-amber-800 p-4">
|
||||
<p class="text-xs font-medium text-amber-700 dark:text-amber-400 mb-1">
|
||||
{{ 'lohn_analyse.totalaufwand_ag' | translate }}
|
||||
</p>
|
||||
<p class="text-xl font-bold text-amber-700 dark:text-amber-400">
|
||||
CHF {{ result().totalCostMonthly | number:'1.2-2' }}
|
||||
</p>
|
||||
<p class="text-xs text-amber-600 dark:text-amber-500 mt-0.5">
|
||||
CHF {{ result().totalCostAnnual | number:'1.2-2' }} / {{ 'lohn_analyse.jahr' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Nettolohn AN -->
|
||||
<div class="rounded-lg bg-emerald-50 border border-emerald-200 dark:bg-emerald-900/20 dark:border-emerald-800 p-4">
|
||||
<p class="text-xs font-medium text-emerald-700 dark:text-emerald-400 mb-1">
|
||||
{{ 'lohn_analyse.nettolohn' | translate }}
|
||||
</p>
|
||||
<p class="text-xl font-bold text-emerald-700 dark:text-emerald-400">
|
||||
CHF {{ result().netMonthly | number:'1.2-2' }}
|
||||
</p>
|
||||
<p class="text-xs text-emerald-600 dark:text-emerald-500 mt-0.5">
|
||||
CHF {{ result().netAnnual | number:'1.2-2' }} / {{ 'lohn_analyse.jahr' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Aufschlüsselung Arbeitgeber -->
|
||||
<div class="rounded-lg bg-white border border-gray-200 dark:bg-gray-800 dark:border-gray-700 overflow-hidden">
|
||||
<div class="px-5 py-3.5 border-b border-gray-100 dark:border-gray-700">
|
||||
<h2 class="text-base font-semibold text-gray-900 dark:text-white">
|
||||
{{ 'lohn_analyse.section_ag' | translate }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
<th class="px-5 py-3 text-left">{{ 'lohn_analyse.col_position' | translate }}</th>
|
||||
<th class="px-4 py-3 text-right">{{ 'lohn_analyse.col_satz' | translate }}</th>
|
||||
<th class="px-4 py-3 text-right">{{ 'lohn_analyse.col_monat' | translate }}</th>
|
||||
<th class="px-5 py-3 text-right">{{ 'lohn_analyse.col_jahr' | translate }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
|
||||
<!-- Bruttolohn -->
|
||||
<tr class="bg-gray-50 dark:bg-gray-700/30">
|
||||
<td class="px-5 py-3 font-medium text-gray-900 dark:text-white">{{ 'lohn_analyse.bruttolohn' | translate }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-400 dark:text-gray-500">—</td>
|
||||
<td class="px-4 py-3 text-right font-medium text-gray-900 dark:text-white">{{ result().grossMonthly | number:'1.2-2' }}</td>
|
||||
<td class="px-5 py-3 text-right font-medium text-gray-900 dark:text-white">{{ result().grossAnnual | number:'1.2-2' }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- AHV/IV/EO -->
|
||||
<tr>
|
||||
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">{{ 'lohn_analyse.ahv_iv_eo' | translate }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">{{ pct(0.053) }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agAhv | number:'1.2-2' }}</td>
|
||||
<td class="px-5 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agAhv * anzahlMonate() | number:'1.2-2' }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- ALV -->
|
||||
<tr>
|
||||
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">{{ 'lohn_analyse.alv' | translate }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">{{ pct(0.011) }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agAlv | number:'1.2-2' }}</td>
|
||||
<td class="px-5 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agAlv * anzahlMonate() | number:'1.2-2' }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- FAK -->
|
||||
<tr>
|
||||
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">
|
||||
{{ 'lohn_analyse.fak' | translate }}
|
||||
<span class="ml-1.5 text-xs text-gray-400 dark:text-gray-500">({{ kanton() }})</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">{{ pct(result().fakRate) }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agFak | number:'1.2-2' }}</td>
|
||||
<td class="px-5 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agFak * anzahlMonate() | number:'1.2-2' }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- VK -->
|
||||
<tr>
|
||||
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">{{ 'lohn_analyse.vk' | translate }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">{{ pct(0.005275) }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agVk | number:'1.2-2' }}</td>
|
||||
<td class="px-5 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agVk * anzahlMonate() | number:'1.2-2' }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- KTV AG (optional) -->
|
||||
@if (agKtvPct() > 0) {
|
||||
<tr>
|
||||
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">{{ 'lohn_analyse.ktv' | translate }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">{{ pct(agKtvPct() / 100) }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agKtvAmt | number:'1.2-2' }}</td>
|
||||
<td class="px-5 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agKtvAmt * anzahlMonate() | number:'1.2-2' }}</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
<!-- BU AG (optional) -->
|
||||
@if (agBuPct() > 0) {
|
||||
<tr>
|
||||
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">{{ 'lohn_analyse.bu' | translate }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">{{ pct(agBuPct() / 100) }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agBuAmt | number:'1.2-2' }}</td>
|
||||
<td class="px-5 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agBuAmt * anzahlMonate() | number:'1.2-2' }}</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
<!-- Total Beiträge AG -->
|
||||
<tr class="bg-amber-50 dark:bg-amber-900/10">
|
||||
<td class="px-5 py-3 font-medium text-amber-800 dark:text-amber-300">{{ 'lohn_analyse.total_beitraege_ag' | translate }}</td>
|
||||
<td class="px-4 py-3 text-right text-amber-700 dark:text-amber-400">{{ pct(result().agTotalRate) }}</td>
|
||||
<td class="px-4 py-3 text-right font-medium text-amber-800 dark:text-amber-300">{{ result().agTotal | number:'1.2-2' }}</td>
|
||||
<td class="px-5 py-3 text-right font-medium text-amber-800 dark:text-amber-300">{{ result().agTotal * anzahlMonate() | number:'1.2-2' }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Totalaufwand -->
|
||||
<tr class="bg-amber-100 dark:bg-amber-900/20 border-t-2 border-amber-200 dark:border-amber-700">
|
||||
<td class="px-5 py-3.5 font-semibold text-amber-900 dark:text-amber-200">{{ 'lohn_analyse.totalaufwand_ag' | translate }}</td>
|
||||
<td class="px-4 py-3.5 text-right text-amber-700 dark:text-amber-400">—</td>
|
||||
<td class="px-4 py-3.5 text-right font-semibold text-amber-900 dark:text-amber-200">{{ result().totalCostMonthly | number:'1.2-2' }}</td>
|
||||
<td class="px-5 py-3.5 text-right font-semibold text-amber-900 dark:text-amber-200">{{ result().totalCostAnnual | number:'1.2-2' }}</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Aufschlüsselung Arbeitnehmer -->
|
||||
<div class="rounded-lg bg-white border border-gray-200 dark:bg-gray-800 dark:border-gray-700 overflow-hidden">
|
||||
<div class="px-5 py-3.5 border-b border-gray-100 dark:border-gray-700">
|
||||
<h2 class="text-base font-semibold text-gray-900 dark:text-white">
|
||||
{{ 'lohn_analyse.section_an' | translate }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
<th class="px-5 py-3 text-left">{{ 'lohn_analyse.col_position' | translate }}</th>
|
||||
<th class="px-4 py-3 text-right">{{ 'lohn_analyse.col_satz' | translate }}</th>
|
||||
<th class="px-4 py-3 text-right">{{ 'lohn_analyse.col_monat' | translate }}</th>
|
||||
<th class="px-5 py-3 text-right">{{ 'lohn_analyse.col_jahr' | translate }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
|
||||
<!-- Bruttolohn -->
|
||||
<tr class="bg-gray-50 dark:bg-gray-700/30">
|
||||
<td class="px-5 py-3 font-medium text-gray-900 dark:text-white">{{ 'lohn_analyse.bruttolohn' | translate }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-400 dark:text-gray-500">—</td>
|
||||
<td class="px-4 py-3 text-right font-medium text-gray-900 dark:text-white">{{ result().grossMonthly | number:'1.2-2' }}</td>
|
||||
<td class="px-5 py-3 text-right font-medium text-gray-900 dark:text-white">{{ result().grossAnnual | number:'1.2-2' }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- AHV/IV/EO -->
|
||||
<tr>
|
||||
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">{{ 'lohn_analyse.ahv_iv_eo' | translate }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">−{{ pct(0.053) }}</td>
|
||||
<td class="px-4 py-3 text-right text-red-600 dark:text-red-400">−{{ result().anAhv | number:'1.2-2' }}</td>
|
||||
<td class="px-5 py-3 text-right text-red-600 dark:text-red-400">−{{ result().anAhv * anzahlMonate() | number:'1.2-2' }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- ALV -->
|
||||
<tr>
|
||||
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">{{ 'lohn_analyse.alv' | translate }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">−{{ pct(0.011) }}</td>
|
||||
<td class="px-4 py-3 text-right text-red-600 dark:text-red-400">−{{ result().anAlv | number:'1.2-2' }}</td>
|
||||
<td class="px-5 py-3 text-right text-red-600 dark:text-red-400">−{{ result().anAlv * anzahlMonate() | number:'1.2-2' }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- KTV AN (optional) -->
|
||||
@if (anKtvPct() > 0) {
|
||||
<tr>
|
||||
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">{{ 'lohn_analyse.ktv' | translate }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">−{{ pct(anKtvPct() / 100) }}</td>
|
||||
<td class="px-4 py-3 text-right text-red-600 dark:text-red-400">−{{ result().anKtvAmt | number:'1.2-2' }}</td>
|
||||
<td class="px-5 py-3 text-right text-red-600 dark:text-red-400">−{{ result().anKtvAmt * anzahlMonate() | number:'1.2-2' }}</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
<!-- NBU AN (optional) -->
|
||||
@if (anNbuPct() > 0) {
|
||||
<tr>
|
||||
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">{{ 'lohn_analyse.nbu' | translate }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">−{{ pct(anNbuPct() / 100) }}</td>
|
||||
<td class="px-4 py-3 text-right text-red-600 dark:text-red-400">−{{ result().anNbuAmt | number:'1.2-2' }}</td>
|
||||
<td class="px-5 py-3 text-right text-red-600 dark:text-red-400">−{{ result().anNbuAmt * anzahlMonate() | number:'1.2-2' }}</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
<!-- Total Abzüge -->
|
||||
<tr class="bg-red-50 dark:bg-red-900/10">
|
||||
<td class="px-5 py-3 font-medium text-red-800 dark:text-red-300">{{ 'lohn_analyse.total_abzuege_an' | translate }}</td>
|
||||
<td class="px-4 py-3 text-right text-red-700 dark:text-red-400">−{{ pct(result().anTotalRate) }}</td>
|
||||
<td class="px-4 py-3 text-right font-medium text-red-800 dark:text-red-300">−{{ result().anTotal | number:'1.2-2' }}</td>
|
||||
<td class="px-5 py-3 text-right font-medium text-red-800 dark:text-red-300">−{{ result().anTotal * anzahlMonate() | number:'1.2-2' }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Nettolohn -->
|
||||
<tr class="bg-emerald-50 dark:bg-emerald-900/20 border-t-2 border-emerald-200 dark:border-emerald-700">
|
||||
<td class="px-5 py-3.5 font-semibold text-emerald-900 dark:text-emerald-200">{{ 'lohn_analyse.nettolohn' | translate }}</td>
|
||||
<td class="px-4 py-3.5 text-right text-emerald-700 dark:text-emerald-400">—</td>
|
||||
<td class="px-4 py-3.5 text-right font-semibold text-emerald-900 dark:text-emerald-200">{{ result().netMonthly | number:'1.2-2' }}</td>
|
||||
<td class="px-5 py-3.5 text-right font-semibold text-emerald-900 dark:text-emerald-200">{{ result().netAnnual | number:'1.2-2' }}</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quellenhinweis -->
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500">
|
||||
{{ 'lohn_analyse.source_hint' | translate }}
|
||||
</p>
|
||||
|
||||
</div><!-- end results -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,102 @@
|
||||
import { Component, computed, signal } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
export const FAK_RATES: Record<string, number> = {
|
||||
AG: 0.0145, AI: 0.016, AR: 0.016, BE: 0.015, BL: 0.013,
|
||||
BS: 0.0165, FR: 0.0227, GE: 0.0222, GL: 0.014, GR: 0.015,
|
||||
JU: 0.0275, LU: 0.0135, NE: 0.018, NW: 0.015, OW: 0.014,
|
||||
SG: 0.018, SH: 0.013, SO: 0.0125, SZ: 0.013, TG: 0.014,
|
||||
TI: 0.016, UR: 0.017, VD: 0.0237, VS: 0.025, ZG: 0.0135,
|
||||
ZH: 0.01025,
|
||||
};
|
||||
|
||||
export const CANTONS = Object.keys(FAK_RATES).sort();
|
||||
|
||||
const AHV_RATE = 0.053;
|
||||
const ALV_RATE = 0.011;
|
||||
const VK_RATE = 0.005275;
|
||||
|
||||
@Component({
|
||||
selector: 'app-salary-analyse',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, TranslateModule],
|
||||
templateUrl: './analyse.html',
|
||||
})
|
||||
export class SalaryAnalyse {
|
||||
readonly cantons = CANTONS;
|
||||
readonly fakRates = FAK_RATES;
|
||||
|
||||
// ── Inputs ────────────────────────────────────────────────────────────────
|
||||
monatslohn = signal(5000);
|
||||
anzahlMonate = signal(12);
|
||||
kanton = signal('ZH');
|
||||
verfahren = signal('ordentlich');
|
||||
|
||||
agKtvPct = signal(0);
|
||||
agBuPct = signal(0);
|
||||
anKtvPct = signal(0);
|
||||
anNbuPct = signal(0);
|
||||
|
||||
showOptional = signal(false);
|
||||
|
||||
// ── Computed ───────────────────────────────────────────────────────────────
|
||||
result = computed(() => {
|
||||
const gross = Math.max(0, this.monatslohn());
|
||||
const monate = Math.min(13, Math.max(1, this.anzahlMonate()));
|
||||
const fak = FAK_RATES[this.kanton()] ?? 0;
|
||||
const agKtv = (this.agKtvPct() ?? 0) / 100;
|
||||
const agBu = (this.agBuPct() ?? 0) / 100;
|
||||
const anKtv = (this.anKtvPct() ?? 0) / 100;
|
||||
const anNbu = (this.anNbuPct() ?? 0) / 100;
|
||||
|
||||
// Arbeitgeber
|
||||
const agAhv = gross * AHV_RATE;
|
||||
const agAlv = gross * ALV_RATE;
|
||||
const agFak = gross * fak;
|
||||
const agVk = gross * VK_RATE;
|
||||
const agKtvAmt = gross * agKtv;
|
||||
const agBuAmt = gross * agBu;
|
||||
const agTotal = agAhv + agAlv + agFak + agVk + agKtvAmt + agBuAmt;
|
||||
const agTotalRate = AHV_RATE + ALV_RATE + fak + VK_RATE + agKtv + agBu;
|
||||
|
||||
// Arbeitnehmer
|
||||
const anAhv = gross * AHV_RATE;
|
||||
const anAlv = gross * ALV_RATE;
|
||||
const anKtvAmt = gross * anKtv;
|
||||
const anNbuAmt = gross * anNbu;
|
||||
const anTotal = anAhv + anAlv + anKtvAmt + anNbuAmt;
|
||||
const anTotalRate = AHV_RATE + ALV_RATE + anKtv + anNbu;
|
||||
|
||||
return {
|
||||
grossMonthly: gross,
|
||||
grossAnnual: gross * monate,
|
||||
fakRate: fak,
|
||||
|
||||
agAhv, agAlv, agFak, agVk, agKtvAmt, agBuAmt,
|
||||
agTotal, agTotalRate,
|
||||
totalCostMonthly: gross + agTotal,
|
||||
totalCostAnnual: (gross + agTotal) * monate,
|
||||
|
||||
anAhv, anAlv, anKtvAmt, anNbuAmt,
|
||||
anTotal, anTotalRate,
|
||||
netMonthly: gross - anTotal,
|
||||
netAnnual: (gross - anTotal) * monate,
|
||||
};
|
||||
});
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────────────
|
||||
pct(rate: number): string {
|
||||
return (rate * 100).toFixed(rate % 0.001 === 0 ? 1 : 3).replace(/\.?0+$/, '') + ' %';
|
||||
}
|
||||
|
||||
setMonatslohn(v: string) { this.monatslohn.set(+v || 0); }
|
||||
setAnzahlMonate(v: string) { this.anzahlMonate.set(Math.min(13, Math.max(1, +v || 12))); }
|
||||
setKanton(v: string) { this.kanton.set(v); }
|
||||
setVerfahren(v: string) { this.verfahren.set(v); }
|
||||
setAgKtvPct(v: string) { this.agKtvPct.set(+v || 0); }
|
||||
setAgBuPct(v: string) { this.agBuPct.set(+v || 0); }
|
||||
setAnKtvPct(v: string) { this.anKtvPct.set(+v || 0); }
|
||||
setAnNbuPct(v: string) { this.anNbuPct.set(+v || 0); }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<div class="p-4 sm:p-6 lg:p-8">
|
||||
<div class="mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{{ 'salary_entwicklung.title' | translate }}</h1>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ 'salary_entwicklung.subtitle' | translate }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Placeholder -->
|
||||
<div class="flex flex-col items-center justify-center py-20 text-center">
|
||||
<div class="w-16 h-16 rounded-full bg-violet-100 dark:bg-violet-900/30 flex items-center justify-center mb-4">
|
||||
<svg class="w-8 h-8 text-violet-600 dark:text-violet-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M12 7a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0V8.414l-4.293 4.293a1 1 0 01-1.414 0L8 10.414l-4.293 4.293a1 1 0 01-1.414-1.414l5-5a1 1 0 011.414 0L11 10.586 14.586 7H12z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">{{ 'salary_entwicklung.coming_soon_title' | translate }}</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 max-w-sm">{{ 'salary_entwicklung.coming_soon_text' | translate }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-salary-entwicklung',
|
||||
standalone: true,
|
||||
imports: [TranslateModule],
|
||||
templateUrl: './entwicklung.html',
|
||||
})
|
||||
export class SalaryEntwicklung {}
|
||||
@@ -0,0 +1,29 @@
|
||||
<div class="flex flex-col h-[calc(100vh-57px)]">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between px-4 sm:px-6 py-3 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 flex-shrink-0">
|
||||
<div>
|
||||
<h1 class="text-lg font-semibold text-gray-900 dark:text-white">{{ 'salarium.title' | translate }}</h1>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">{{ 'salarium.subtitle' | translate }}</p>
|
||||
</div>
|
||||
<a [href]="externalUrl" target="_blank" rel="noopener noreferrer"
|
||||
class="inline-flex items-center gap-1.5 rounded-lg border border-gray-200 bg-white px-3 py-1.5 text-xs font-medium text-gray-700
|
||||
hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600 transition-colors">
|
||||
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M18 14v4.833A1.166 1.166 0 0 1 16.833 20H5.167A1.167 1.167 0 0 1 4 18.833V7.167A1.166 1.166 0 0 1 5.167 6h4.618m4.447-2H20v5.768m-7.889 2.121 7.778-7.778"/>
|
||||
</svg>
|
||||
{{ 'salarium.open_external' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- iFrame -->
|
||||
<iframe
|
||||
[src]="safeUrl"
|
||||
class="flex-1 w-full border-0"
|
||||
title="Salarium – BFS Lohnrechner"
|
||||
loading="lazy"
|
||||
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox">
|
||||
</iframe>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-salarium',
|
||||
standalone: true,
|
||||
imports: [TranslateModule],
|
||||
templateUrl: './salarium.html',
|
||||
})
|
||||
export class Salarium {
|
||||
private sanitizer = inject(DomSanitizer);
|
||||
|
||||
readonly externalUrl = 'https://www.salarium.bfs.admin.ch/';
|
||||
readonly safeUrl: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.externalUrl);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ export class SidebarService {
|
||||
mobileOpen = signal(false);
|
||||
budgetsOpen = signal(false);
|
||||
accountsOpen = signal(false);
|
||||
salaryOpen = signal(false);
|
||||
|
||||
toggle() {
|
||||
this.collapsed.update(v => !v);
|
||||
@@ -31,6 +32,10 @@ export class SidebarService {
|
||||
this.accountsOpen.update(v => !v);
|
||||
}
|
||||
|
||||
toggleSalary() {
|
||||
this.salaryOpen.update(v => !v);
|
||||
}
|
||||
|
||||
toggleFlyout(name: string) {
|
||||
this.openFlyout.update(current => current === name ? null : name);
|
||||
}
|
||||
|
||||
@@ -144,7 +144,67 @@
|
||||
"calendar": "Kalender",
|
||||
"accounts": "Konten",
|
||||
"revenue_accounts": "Einnahmekonten",
|
||||
"transactions": "Transaktionen"
|
||||
"transactions": "Transaktionen",
|
||||
"salary": "Lohn",
|
||||
"salarium": "Salarium",
|
||||
"salary_analyse": "Analyse",
|
||||
"salary_entwicklung": "Entwicklung"
|
||||
},
|
||||
"salarium": {
|
||||
"title": "Salarium",
|
||||
"subtitle": "Statistischer Lohnrechner des Bundesamts für Statistik (BFS).",
|
||||
"open_external": "Im neuen Tab öffnen"
|
||||
},
|
||||
"salary_analyse": {
|
||||
"title": "Lohnanalyse",
|
||||
"subtitle": "Vergleiche deinen aktuellen Lohn mit dem Marktdurchschnitt.",
|
||||
"coming_soon_title": "Lohnanalyse in Entwicklung",
|
||||
"coming_soon_text": "Hier kannst du deinen Lohn mit Branchen- und Regionaldaten vergleichen."
|
||||
},
|
||||
"salary_entwicklung": {
|
||||
"title": "Lohnentwicklung",
|
||||
"subtitle": "Verfolge deine Lohnentwicklung über die Jahre.",
|
||||
"coming_soon_title": "Lohnentwicklung in Entwicklung",
|
||||
"coming_soon_text": "Visualisiere deine Lohnhistorie und erkenne Trends über die Zeit."
|
||||
},
|
||||
"lohn_analyse": {
|
||||
"title": "Lohnanalyse",
|
||||
"subtitle": "Berechne Brutto- und Nettolohn sowie den Totalaufwand des Arbeitgebers nach Schweizer Recht.",
|
||||
"section_basics": "Grundlagen",
|
||||
"label_monatslohn": "Monatslohn",
|
||||
"label_monate": "Anzahl Monate",
|
||||
"monate": "Monate",
|
||||
"label_kanton": "Kanton",
|
||||
"fak_rate_hint": "FAK-Beitragssatz:",
|
||||
"label_verfahren": "Abrechnungsverfahren",
|
||||
"verfahren_ordentlich": "Ordentliches Verfahren",
|
||||
"verfahren_vereinfacht": "Vereinfachtes Verfahren",
|
||||
"verfahren_quellensteuer": "Ordentliches mit Quellensteuer",
|
||||
"verfahren_hint": "Dieses Verfahren wird noch nicht vollständig berechnet — es werden die Standardsätze verwendet.",
|
||||
"section_optional": "Weitere Sätze (KTV / BU / NBU)",
|
||||
"optional_hint": "KTV- und Berufsunfall-Sätze sind betriebsabhängig. Leer lassen wenn unbekannt.",
|
||||
"beitraege_ag": "Arbeitgeber",
|
||||
"abzuege_an": "Arbeitnehmer",
|
||||
"ktv": "KTV",
|
||||
"bu": "BU",
|
||||
"nbu": "NBU",
|
||||
"section_ag": "Kosten Arbeitgeber",
|
||||
"section_an": "Abzüge Arbeitnehmer",
|
||||
"bruttolohn": "Bruttolohn",
|
||||
"ahv_iv_eo": "AHV / IV / EO",
|
||||
"alv": "ALV",
|
||||
"fak": "FAK",
|
||||
"vk": "Verwaltungskosten (VK)",
|
||||
"total_beitraege_ag": "Total Beiträge Arbeitgeber",
|
||||
"totalaufwand_ag": "Totalaufwand Arbeitgeber",
|
||||
"total_abzuege_an": "Total Abzüge Arbeitnehmer",
|
||||
"nettolohn": "Nettolohn",
|
||||
"col_position": "Position",
|
||||
"col_satz": "Satz",
|
||||
"col_monat": "/ Monat",
|
||||
"col_jahr": "/ Jahr",
|
||||
"jahr": "Jahr",
|
||||
"source_hint": "Quelle: Lohnbudget Monatslohn, Bundesamt für Sozialversicherungen (BSV). Gilt für das ordentliche Abrechnungsverfahren."
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
|
||||
@@ -144,7 +144,67 @@
|
||||
"calendar": "Calendar",
|
||||
"accounts": "Accounts",
|
||||
"revenue_accounts": "Revenue Accounts",
|
||||
"transactions": "Transactions"
|
||||
"transactions": "Transactions",
|
||||
"salary": "Salary",
|
||||
"salarium": "Salarium",
|
||||
"salary_analyse": "Analysis",
|
||||
"salary_entwicklung": "Development"
|
||||
},
|
||||
"salarium": {
|
||||
"title": "Salarium",
|
||||
"subtitle": "Statistical salary calculator by the Federal Statistical Office (FSO).",
|
||||
"open_external": "Open in new tab"
|
||||
},
|
||||
"salary_analyse": {
|
||||
"title": "Salary Analysis",
|
||||
"subtitle": "Compare your current salary with the market average.",
|
||||
"coming_soon_title": "Salary Analysis in Development",
|
||||
"coming_soon_text": "Here you will be able to compare your salary with industry and regional benchmarks."
|
||||
},
|
||||
"salary_entwicklung": {
|
||||
"title": "Salary Development",
|
||||
"subtitle": "Track your salary development over the years.",
|
||||
"coming_soon_title": "Salary Development in Development",
|
||||
"coming_soon_text": "Visualize your salary history and identify trends over time."
|
||||
},
|
||||
"lohn_analyse": {
|
||||
"title": "Salary Analysis",
|
||||
"subtitle": "Calculate gross and net salary as well as the total employer cost under Swiss law.",
|
||||
"section_basics": "Basics",
|
||||
"label_monatslohn": "Monthly Salary",
|
||||
"label_monate": "Number of Months",
|
||||
"monate": "Months",
|
||||
"label_kanton": "Canton",
|
||||
"fak_rate_hint": "FAK contribution rate:",
|
||||
"label_verfahren": "Billing Method",
|
||||
"verfahren_ordentlich": "Standard Method",
|
||||
"verfahren_vereinfacht": "Simplified Method",
|
||||
"verfahren_quellensteuer": "Standard with Withholding Tax",
|
||||
"verfahren_hint": "This method is not yet fully calculated — standard rates are used.",
|
||||
"section_optional": "Additional Rates (KTV / BU / NBU)",
|
||||
"optional_hint": "KTV and accident insurance rates depend on the employer. Leave empty if unknown.",
|
||||
"beitraege_ag": "Employer",
|
||||
"abzuege_an": "Employee",
|
||||
"ktv": "KTV",
|
||||
"bu": "BU",
|
||||
"nbu": "NBU",
|
||||
"section_ag": "Employer Costs",
|
||||
"section_an": "Employee Deductions",
|
||||
"bruttolohn": "Gross Salary",
|
||||
"ahv_iv_eo": "AHV / IV / EO",
|
||||
"alv": "ALV",
|
||||
"fak": "FAK",
|
||||
"vk": "Admin Costs (VK)",
|
||||
"total_beitraege_ag": "Total Employer Contributions",
|
||||
"totalaufwand_ag": "Total Employer Cost",
|
||||
"total_abzuege_an": "Total Employee Deductions",
|
||||
"nettolohn": "Net Salary",
|
||||
"col_position": "Position",
|
||||
"col_satz": "Rate",
|
||||
"col_monat": "/ Month",
|
||||
"col_jahr": "/ Year",
|
||||
"jahr": "Year",
|
||||
"source_hint": "Source: Monthly Salary Budget, Federal Social Insurance Office (FSIO). Applies to the standard billing method."
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
|
||||
@@ -144,7 +144,67 @@
|
||||
"calendar": "Calendrier",
|
||||
"accounts": "Comptes",
|
||||
"revenue_accounts": "Comptes de revenus",
|
||||
"transactions": "Transactions"
|
||||
"transactions": "Transactions",
|
||||
"salary": "Salaire",
|
||||
"salarium": "Salarium",
|
||||
"salary_analyse": "Analyse",
|
||||
"salary_entwicklung": "Évolution"
|
||||
},
|
||||
"salarium": {
|
||||
"title": "Salarium",
|
||||
"subtitle": "Calculateur de salaire statistique de l'Office fédéral de la statistique (OFS).",
|
||||
"open_external": "Ouvrir dans un nouvel onglet"
|
||||
},
|
||||
"salary_analyse": {
|
||||
"title": "Analyse salariale",
|
||||
"subtitle": "Comparez votre salaire actuel avec la moyenne du marché.",
|
||||
"coming_soon_title": "Analyse salariale en développement",
|
||||
"coming_soon_text": "Vous pourrez ici comparer votre salaire avec les données sectorielles et régionales."
|
||||
},
|
||||
"salary_entwicklung": {
|
||||
"title": "Évolution salariale",
|
||||
"subtitle": "Suivez l'évolution de votre salaire au fil des années.",
|
||||
"coming_soon_title": "Évolution salariale en développement",
|
||||
"coming_soon_text": "Visualisez votre historique salarial et identifiez les tendances au fil du temps."
|
||||
},
|
||||
"lohn_analyse": {
|
||||
"title": "Analyse salariale",
|
||||
"subtitle": "Calculez le salaire brut, net et le coût total de l'employeur selon le droit suisse.",
|
||||
"section_basics": "Données de base",
|
||||
"label_monatslohn": "Salaire mensuel",
|
||||
"label_monate": "Nombre de mois",
|
||||
"monate": "mois",
|
||||
"label_kanton": "Canton",
|
||||
"fak_rate_hint": "Taux de cotisation CAF :",
|
||||
"label_verfahren": "Procédure de décompte",
|
||||
"verfahren_ordentlich": "Procédure ordinaire",
|
||||
"verfahren_vereinfacht": "Procédure simplifiée",
|
||||
"verfahren_quellensteuer": "Procédure ordinaire avec impôt à la source",
|
||||
"verfahren_hint": "Cette procédure n'est pas encore entièrement calculée — les taux standard sont utilisés.",
|
||||
"section_optional": "Taux supplémentaires (IJM / AA)",
|
||||
"optional_hint": "Les taux IJM et accidents dépendent de l'employeur. Laisser vide si inconnu.",
|
||||
"beitraege_ag": "Employeur",
|
||||
"abzuege_an": "Employé",
|
||||
"ktv": "IJM",
|
||||
"bu": "AA pro.",
|
||||
"nbu": "AA non-pro.",
|
||||
"section_ag": "Coûts employeur",
|
||||
"section_an": "Déductions employé",
|
||||
"bruttolohn": "Salaire brut",
|
||||
"ahv_iv_eo": "AVS / AI / APG",
|
||||
"alv": "AC",
|
||||
"fak": "CAF",
|
||||
"vk": "Frais admin. (VK)",
|
||||
"total_beitraege_ag": "Total cotisations employeur",
|
||||
"totalaufwand_ag": "Coût total employeur",
|
||||
"total_abzuege_an": "Total déductions employé",
|
||||
"nettolohn": "Salaire net",
|
||||
"col_position": "Position",
|
||||
"col_satz": "Taux",
|
||||
"col_monat": "/ Mois",
|
||||
"col_jahr": "/ An",
|
||||
"jahr": "An",
|
||||
"source_hint": "Source : Budget salaire mensuel, Office fédéral des assurances sociales (OFAS). Valable pour la procédure de décompte ordinaire."
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Tableau de bord",
|
||||
|
||||
@@ -144,7 +144,67 @@
|
||||
"calendar": "Calendario",
|
||||
"accounts": "Conti",
|
||||
"revenue_accounts": "Conti entrate",
|
||||
"transactions": "Transazioni"
|
||||
"transactions": "Transazioni",
|
||||
"salary": "Stipendio",
|
||||
"salarium": "Salarium",
|
||||
"salary_analyse": "Analisi",
|
||||
"salary_entwicklung": "Sviluppo"
|
||||
},
|
||||
"salarium": {
|
||||
"title": "Salarium",
|
||||
"subtitle": "Calcolatore statistico degli stipendi dell'Ufficio federale di statistica (UST).",
|
||||
"open_external": "Apri in una nuova scheda"
|
||||
},
|
||||
"salary_analyse": {
|
||||
"title": "Analisi stipendio",
|
||||
"subtitle": "Confronta il tuo stipendio attuale con la media di mercato.",
|
||||
"coming_soon_title": "Analisi stipendio in sviluppo",
|
||||
"coming_soon_text": "Qui potrai confrontare il tuo stipendio con i dati del settore e della regione."
|
||||
},
|
||||
"salary_entwicklung": {
|
||||
"title": "Sviluppo stipendio",
|
||||
"subtitle": "Monitora lo sviluppo del tuo stipendio nel tempo.",
|
||||
"coming_soon_title": "Sviluppo stipendio in sviluppo",
|
||||
"coming_soon_text": "Visualizza la tua storia salariale e identifica le tendenze nel tempo."
|
||||
},
|
||||
"lohn_analyse": {
|
||||
"title": "Analisi stipendio",
|
||||
"subtitle": "Calcola lo stipendio lordo, netto e il costo totale del datore di lavoro secondo il diritto svizzero.",
|
||||
"section_basics": "Dati di base",
|
||||
"label_monatslohn": "Stipendio mensile",
|
||||
"label_monate": "Numero di mesi",
|
||||
"monate": "mesi",
|
||||
"label_kanton": "Cantone",
|
||||
"fak_rate_hint": "Aliquota CAF:",
|
||||
"label_verfahren": "Procedura di conteggio",
|
||||
"verfahren_ordentlich": "Procedura ordinaria",
|
||||
"verfahren_vereinfacht": "Procedura semplificata",
|
||||
"verfahren_quellensteuer": "Procedura ordinaria con imposta alla fonte",
|
||||
"verfahren_hint": "Questa procedura non è ancora completamente calcolata — vengono utilizzate le aliquote standard.",
|
||||
"section_optional": "Aliquote aggiuntive (IJM / LAINF)",
|
||||
"optional_hint": "Le aliquote IJM e infortuni dipendono dal datore di lavoro. Lasciare vuoto se sconosciuto.",
|
||||
"beitraege_ag": "Datore di lavoro",
|
||||
"abzuege_an": "Lavoratore",
|
||||
"ktv": "IJM",
|
||||
"bu": "LAINF pro.",
|
||||
"nbu": "LAINF non-pro.",
|
||||
"section_ag": "Costi datore di lavoro",
|
||||
"section_an": "Deduzioni lavoratore",
|
||||
"bruttolohn": "Stipendio lordo",
|
||||
"ahv_iv_eo": "AVS / AI / IPG",
|
||||
"alv": "AD",
|
||||
"fak": "CAF",
|
||||
"vk": "Spese amm. (VK)",
|
||||
"total_beitraege_ag": "Totale contributi datore di lavoro",
|
||||
"totalaufwand_ag": "Costo totale datore di lavoro",
|
||||
"total_abzuege_an": "Totale deduzioni lavoratore",
|
||||
"nettolohn": "Stipendio netto",
|
||||
"col_position": "Posizione",
|
||||
"col_satz": "Aliquota",
|
||||
"col_monat": "/ Mese",
|
||||
"col_jahr": "/ Anno",
|
||||
"jahr": "Anno",
|
||||
"source_hint": "Fonte: Budget stipendio mensile, Ufficio federale delle assicurazioni sociali (UFAS). Valido per la procedura di conteggio ordinaria."
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
|
||||
Reference in New Issue
Block a user