diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 86d90dc..99cf931 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -11,6 +11,7 @@ import { PeselComponent } from './components/pesel/pesel.component';
import { NipComponent } from './components/nip/nip.component';
import { RegonComponent } from './components/regon/regon.component';
import { IbanComponent } from './components/iban/iban.component';
+import { IdentityCardComponent } from './components/identitycard/identitycard.component';
import {NgOptimizedImage} from "@angular/common";
@NgModule({
@@ -20,7 +21,8 @@ import {NgOptimizedImage} from "@angular/common";
PeselComponent,
NipComponent,
RegonComponent,
- IbanComponent
+ IbanComponent,
+ IdentityCardComponent
],
imports: [
BrowserModule,
diff --git a/src/app/components/app/app.component.html b/src/app/components/app/app.component.html
index 0e42051..78e1cab 100644
--- a/src/app/components/app/app.component.html
+++ b/src/app/components/app/app.component.html
@@ -3,5 +3,6 @@
+
-
\ No newline at end of file
+
diff --git a/src/app/components/identitycard/identitycard.component.html b/src/app/components/identitycard/identitycard.component.html
new file mode 100644
index 0000000..3356630
--- /dev/null
+++ b/src/app/components/identitycard/identitycard.component.html
@@ -0,0 +1,17 @@
+
diff --git a/src/app/components/identitycard/identitycard.component.scss b/src/app/components/identitycard/identitycard.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/components/identitycard/identitycard.component.ts b/src/app/components/identitycard/identitycard.component.ts
new file mode 100644
index 0000000..bf06aac
--- /dev/null
+++ b/src/app/components/identitycard/identitycard.component.ts
@@ -0,0 +1,35 @@
+import { Component, OnInit } from '@angular/core';
+import { AbstractControl, FormControl } from '@angular/forms';
+import { IdentityCardService } from 'src/app/service/identity-card.service';
+import { ClipboardService } from 'src/app/service/gui/clipboard.service';
+
+@Component({
+ selector: 'app-identity-card',
+ templateUrl: './identitycard.component.html',
+ styleUrls: ['./identitycard.component.scss'],
+ standalone: false
+})
+export class IdentityCardComponent implements OnInit {
+ public valueField: FormControl;
+
+ constructor(private identityCardService: IdentityCardService, private clipboardService: ClipboardService) {
+ this.valueField = new FormControl('');
+ this.valueField.setValidators([(control: AbstractControl) => this.identityCardService.validateIdentityCard(control.value)]);
+ }
+
+ ngOnInit(): void {
+ this.generate();
+ }
+
+ generate(): void {
+ this.valueField.setValue(this.identityCardService.generateIdentityCard());
+ }
+
+ copyToClipboard(): void {
+ this.clipboardService.copyToClipboard(this.valueField.value);
+ }
+
+ navigateToDocs(): void {
+ window.open('https://pl.wikipedia.org/wiki/Dow%C3%B3d_osobisty_w_Polsce#Numer_dowodu_osobistego', "_blank");
+ }
+}
diff --git a/src/app/service/identity-card.service.ts b/src/app/service/identity-card.service.ts
new file mode 100644
index 0000000..99a3492
--- /dev/null
+++ b/src/app/service/identity-card.service.ts
@@ -0,0 +1,85 @@
+import { Injectable } from '@angular/core';
+import { ValidationErrors } from '@angular/forms';
+import { CommonService } from './common.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class IdentityCardService {
+ private static readonly weights: number[] = [7, 3, 1, 0, 7, 3, 1, 7, 3];
+ private static readonly letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+ constructor(private common: CommonService) { }
+
+ public generateIdentityCard(): string {
+ let result = "";
+ // Generate 3 letters
+ for (let i = 0; i < 3; i++) {
+ result += IdentityCardService.letters.charAt(this.common.getRandomInt(0, 25));
+ }
+ // Generate 5 digits (for positions 4-8)
+ let digits = "";
+ for (let i = 0; i < 5; i++) {
+ digits += this.common.getRandomInt(0, 9).toString();
+ }
+
+ // Calculate sum
+ let sum = 0;
+ // Letters
+ for (let i = 0; i < 3; i++) {
+ sum += this.letterToValue(result.charAt(i)) * IdentityCardService.weights[i];
+ }
+ // Digits
+ for (let i = 0; i < 5; i++) {
+ sum += parseInt(digits.charAt(i), 10) * IdentityCardService.weights[i + 4];
+ }
+
+ const controlDigit = sum % 10;
+
+ return result + controlDigit + digits;
+ }
+
+ public validateIdentityCard(id: string): ValidationErrors | null {
+ const errors: ValidationErrors = {};
+ if (!id) {
+ errors.required = true;
+ return errors;
+ }
+
+ const upperId = id.toUpperCase();
+
+ if (upperId.length !== 9) {
+ if (upperId.length < 9) errors.tooShort = true;
+ else errors.tooLong = true;
+ }
+
+ const pattern = /^[A-Z]{3}[0-9]{6}$/;
+ if (!pattern.test(upperId)) {
+ errors.invalidPattern = true;
+ }
+
+ if (Object.keys(errors).length > 0) return errors;
+
+ // Control digit validation
+ let sum = 0;
+ for (let i = 0; i < 9; i++) {
+ if (i === 3) continue; // skip control digit
+ const char = upperId.charAt(i);
+ const val = i < 3 ? this.letterToValue(char) : parseInt(char, 10);
+ sum += val * IdentityCardService.weights[i];
+ }
+
+ const calculatedControl = sum % 10;
+ const actualControl = parseInt(upperId.charAt(3), 10);
+
+ if (calculatedControl !== actualControl) {
+ errors.invalidControlDigit = true;
+ }
+
+ return Object.keys(errors).length > 0 ? errors : null;
+ }
+
+ private letterToValue(letter: string): number {
+ return IdentityCardService.letters.indexOf(letter.toUpperCase()) + 10;
+ }
+}