This commit is contained in:
Dwi Swandhana
2026-07-22 05:18:55 +07:00
parent 6d0f9a8bf6
commit 32db765b98
50 changed files with 2189 additions and 1909 deletions
@@ -0,0 +1,124 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class SyncPoliFromJson extends Command
{
protected $signature = 'poli:sync-json {--path= : Path file JSON poli, default database/seeders/data/poli.json} {--dry-run : Cek data tanpa menulis ke database}';
protected $description = 'Update atau create data poli dari file JSON berdasarkan id tanpa truncate.';
public function handle(): int
{
$path = $this->resolvePath($this->option('path') ?: database_path('seeders/data/poli.json'));
$data = $this->readJson($path);
$updated = 0;
$created = 0;
$unchanged = 0;
DB::transaction(function () use ($data, &$updated, &$created, &$unchanged) {
foreach ($data as $row) {
$id = (int) $row['id'];
$payload = [
'poli' => $row['poli'] ?? null,
'subpoli' => $row['subpoli'] ?? null,
'subsubpoli' => $row['subsubpoli'] ?? null,
'modaliti' => $row['modaliti'] ?? null,
'kelompok' => $row['kelompok'] ?? null,
'modaliti2' => isset($row['modaliti2']) ? (string) $row['modaliti2'] : null,
'created_at' => $row['created_at'] ?? now(),
'updated_at' => $row['updated_at'] ?? now(),
];
$existing = DB::table('poli')->where('id', $id)->first();
if ($existing === null) {
$created++;
if (!$this->option('dry-run')) {
DB::table('poli')->insert(array_merge(['id' => $id], $payload));
}
continue;
}
if ($this->isSameRow($existing, $payload)) {
$unchanged++;
continue;
}
$updated++;
if (!$this->option('dry-run')) {
DB::table('poli')->where('id', $id)->update($payload);
}
}
});
$mode = $this->option('dry-run') ? 'DRY RUN' : 'DONE';
$this->info("{$mode}: {$created} created, {$updated} updated, {$unchanged} unchanged.");
return self::SUCCESS;
}
private function resolvePath(string $path): string
{
if ($path !== '' && $path[0] === '/') {
return $path;
}
return base_path($path);
}
private function readJson(string $path): array
{
if (!is_file($path)) {
throw new \RuntimeException("File JSON poli tidak ditemukan: {$path}");
}
$json = file_get_contents($path);
if ($json === false) {
throw new \RuntimeException("Gagal membaca file JSON poli: {$path}");
}
$data = json_decode($json, true);
if (!is_array($data)) {
throw new \RuntimeException("Format JSON poli tidak valid: {$path}");
}
$ids = [];
foreach ($data as $index => $row) {
if (!is_array($row) || !isset($row['id'])) {
throw new \RuntimeException('Data poli baris '.($index + 1).' tidak memiliki id.');
}
$id = (int) $row['id'];
if (isset($ids[$id])) {
throw new \RuntimeException("Duplikasi id poli pada JSON: {$id}");
}
$ids[$id] = true;
}
return $data;
}
private function isSameRow(object $existing, array $payload): bool
{
foreach ($payload as $key => $value) {
if ((string) ($existing->{$key} ?? '') !== (string) ($value ?? '')) {
return false;
}
}
return true;
}
}
@@ -846,7 +846,7 @@ class DokterController extends Controller
return 2;
}
if (str_starts_with($normalized, 'data bd di terima')) {
if (str_starts_with($normalized, 'data bd di terima') || str_starts_with($normalized, 'data bd diterima')) {
return 3;
}
@@ -858,26 +858,31 @@ class DokterController extends Controller
return 5;
}
if (str_starts_with($normalized, 'otor maldi (un verified)')) {
if (str_starts_with($normalized, 'data malditof diterima') || str_starts_with($normalized, 'data malditof di terima')) {
return 6;
}
if (str_starts_with($normalized, 'preliminary results')) {
if (str_starts_with($normalized, 'otor maldi (un verified)')) {
return 7;
}
if (str_starts_with($normalized, 'data vitek di terima')) {
if (str_starts_with($normalized, 'preliminary results')) {
return 8;
}
if (str_starts_with($normalized, 'expertise saved (un verified)')) {
if (str_starts_with($normalized, 'data vitek di terima') || str_starts_with($normalized, 'data vitek diterima')) {
return 9;
}
if (in_array($normalized, ['expertise', 'selesai', 'final result'], true)) {
if (str_starts_with($normalized, 'id/ast pending result') || $normalized === 'sedang id+ast') {
return 10;
}
if (str_starts_with($normalized, 'expertise saved (un verified)')
|| in_array($normalized, ['expertise', 'selesai', 'final result'], true)) {
return 11;
}
return null;
}
protected function normalizePeriksaStatus($status): string {
@@ -897,10 +902,6 @@ class DokterController extends Controller
'menunggu kultur yg lain',
'menunggu kultur yang lain',
'proses identifikasi dan uji kepekaan',
'id/ast pending result',
'data malditof diterima',
'data malditof di terima',
'sedang id+ast',
'sedang malditof',
'pertumbuhan primer',
];
+6 -3
View File
@@ -8,11 +8,14 @@ class Poli extends Model
{
protected $table = "poli";
protected $fillable = [
'id',
'poli',
'subpoli',
'subsubpoli',
'modaliti',
'kelompok',
'modaliti2',
'modaliti',
'kelompok',
'modaliti2',
'created_at',
'updated_at',
];
}
@@ -0,0 +1,72 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
if (!Schema::hasTable('poli')) {
Schema::create('poli', function (Blueprint $table) {
$table->id();
$table->string('poli', 250)->nullable();
$table->string('subpoli', 250)->nullable();
$table->string('subsubpoli', 250)->nullable();
$table->string('modaliti', 150)->nullable();
$table->string('kelompok', 150)->nullable();
$table->string('modaliti2', 150)->nullable();
$table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->nullable();
});
return;
}
Schema::table('poli', function (Blueprint $table) {
if (!Schema::hasColumn('poli', 'poli')) {
$table->string('poli', 250)->nullable();
}
if (!Schema::hasColumn('poli', 'subpoli')) {
$table->string('subpoli', 250)->nullable();
}
if (!Schema::hasColumn('poli', 'subsubpoli')) {
$table->string('subsubpoli', 250)->nullable();
}
if (!Schema::hasColumn('poli', 'modaliti')) {
$table->string('modaliti', 150)->nullable();
}
if (!Schema::hasColumn('poli', 'kelompok')) {
$table->string('kelompok', 150)->nullable();
}
if (!Schema::hasColumn('poli', 'modaliti2')) {
$table->string('modaliti2', 150)->nullable();
}
if (!Schema::hasColumn('poli', 'created_at')) {
$table->timestamp('created_at')->nullable();
}
if (!Schema::hasColumn('poli', 'updated_at')) {
$table->timestamp('updated_at')->nullable();
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// Kolom poli dipakai data production, rollback tidak menghapus kolom.
}
};
File diff suppressed because it is too large Load Diff
+14 -2
View File
@@ -1,6 +1,18 @@
# MyLIS Flutter
# Mikrobiology Laboratory Information System (MyLIS)
MyLIS Flutter adalah aplikasi pendamping untuk Laravel LIS lokal. Aplikasi ini fokus pada akses mobile/desktop untuk login, dashboard Early Warning Sistem, daftar pemeriksaan, detail pemeriksaan, dan pengisian expertise per template DLP.
MyLIS Flutter adalah aplikasi pendamping untuk Laravel LIS lokal milik Rumah Sakit Umum Daerah Dr. Saiful Anwar. Aplikasi ini fokus pada akses mobile/desktop untuk login, dashboard Early Warning Sistem, daftar pemeriksaan, detail pemeriksaan, dan pengisian expertise per template DLP.
Nama panjang aplikasi:
```text
Mikrobiology Laboratory Information System (MyLIS)
```
Pemilik aplikasi:
```text
Rumah Sakit Umum Daerah Dr. Saiful Anwar
```
Default URL Laravel lokal:
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:label="mylis"
android:label="MyLIS"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 20 KiB

+2 -2
View File
@@ -7,7 +7,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Mylis</string>
<string>MyLIS</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@@ -15,7 +15,7 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>mylis</string>
<string>MyLIS</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
+8 -1
View File
@@ -25,6 +25,13 @@ part '../screens/expertise/expertise_form.dart';
part '../data/expertise_data.dart';
part '../widgets/common_widgets.dart';
const String kAppShortName = 'MyLIS';
const String kAppLongName =
'Mikrobiology Laboratory Information System (MyLIS)';
const String kHospitalName = 'Rumah Sakit Umum Daerah Dr. Saiful Anwar';
const String kAppLogoAsset = 'assets/branding/logo.png';
const String kHospitalLogoAsset = 'assets/branding/logo_rssa.png';
const String kDefaultBaseUrl = String.fromEnvironment(
'LIS_BASE_URL',
defaultValue: 'https://lis.swandhana.test/',
@@ -36,7 +43,7 @@ class MyLisApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MyLIS Mobile',
title: kAppLongName,
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
@@ -43,7 +43,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Dashboard'),
title: const Text(kAppShortName),
actions: [
IconButton(
onPressed: _reload,
@@ -131,9 +131,44 @@ class HeaderPanel extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 54,
height: 54,
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Image.asset(kHospitalLogoAsset, fit: BoxFit.contain),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
kAppShortName,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Colors.white,
fontWeight: FontWeight.w800,
),
),
const Text(
kHospitalName,
style: TextStyle(color: Colors.white70, fontSize: 12),
),
],
),
),
],
),
const SizedBox(height: 14),
Text(
user['nama']?.toString() ?? '-',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Colors.white,
fontWeight: FontWeight.w700,
),
+25 -6
View File
@@ -120,19 +120,38 @@ class _LoginScreenState extends State<LoginScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Icon(
Icons.biotech_outlined,
size: 72,
color: Color(0xFF0F766E),
Center(
child: Image.asset(
kAppLogoAsset,
height: 96,
fit: BoxFit.contain,
),
),
const SizedBox(height: 16),
Text(
'MyLIS Mobile',
kAppLongName,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium
?.copyWith(fontWeight: FontWeight.w700),
),
const SizedBox(height: 6),
const SizedBox(height: 12),
Center(
child: Image.asset(
kHospitalLogoAsset,
height: 54,
fit: BoxFit.contain,
),
),
const SizedBox(height: 8),
Text(
kHospitalName,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w700,
color: const Color(0xFF0F766E),
),
),
const SizedBox(height: 8),
Text(
'Login petugas',
textAlign: TextAlign.center,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 520 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

+2 -2
View File
@@ -5,10 +5,10 @@
// 'flutter create' template.
// The application's name. By default this is also the title of the Flutter window.
PRODUCT_NAME = mylis
PRODUCT_NAME = MyLIS
// The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = com.example.mylis
// The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved.
PRODUCT_COPYRIGHT = Copyright © 2026 Rumah Sakit Umum Daerah Dr. Saiful Anwar. All rights reserved.
+5 -1
View File
@@ -1,5 +1,5 @@
name: mylis
description: "A new Flutter project."
description: "Mikrobiology Laboratory Information System (MyLIS)"
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
@@ -60,6 +60,10 @@ flutter:
# the material Icons class.
uses-material-design: true
assets:
- assets/branding/logo.png
- assets/branding/logo_rssa.png
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
+4 -1
View File
@@ -17,7 +17,10 @@ void main() {
await tester.pumpWidget(const MyLisApp());
await tester.pump();
expect(find.text('MyLIS Mobile'), findsOneWidget);
expect(
find.text('Mikrobiology Laboratory Information System (MyLIS)'),
findsOneWidget,
);
expect(find.text('Login'), findsOneWidget);
expect(find.text('Username'), findsOneWidget);
});
Binary file not shown.

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 80 KiB

+3 -3
View File
@@ -18,18 +18,18 @@
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<meta name="description" content="Mikrobiology Laboratory Information System (MyLIS) milik Rumah Sakit Umum Daerah Dr. Saiful Anwar.">
<!-- iOS meta tags & icons -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="mylis">
<meta name="apple-mobile-web-app-title" content="MyLIS">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>mylis</title>
<title>Mikrobiology Laboratory Information System (MyLIS)</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
+5 -5
View File
@@ -1,11 +1,11 @@
{
"name": "mylis",
"short_name": "mylis",
"name": "Mikrobiology Laboratory Information System (MyLIS)",
"short_name": "MyLIS",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"background_color": "#F7FAF9",
"theme_color": "#0F766E",
"description": "Mikrobiology Laboratory Information System (MyLIS) milik Rumah Sakit Umum Daerah Dr. Saiful Anwar.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [