Commit a33dfdce authored by Sugar Pramana's avatar Sugar Pramana

Penyesuaian setelah showing dengan Pak cornel

parents
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Panduan Build APK Android
## Langkah-langkah Build APK
### 1. Install Dependencies
```bash
npm install
```
### 2. Build Aplikasi React
```bash
npm run build
```
### 3. Sync dengan Capacitor
```bash
npx cap sync android
```
### 4. Buka di Android Studio
```bash
npx cap open android
```
### 5. Build APK di Android Studio
Setelah Android Studio terbuka:
1. Tunggu Gradle sync selesai
2. Pilih menu **Build > Build Bundle(s) / APK(s) > Build APK(s)**
3. Tunggu proses build selesai
4. Klik **locate** pada notifikasi untuk membuka folder APK
5. APK tersimpan di: `android/app/build/outputs/apk/debug/app-debug.apk`
## Alternatif: Build via Command Line
Jika sudah setup Android SDK di PATH:
```bash
cd android
./gradlew assembleDebug
```
APK akan tersimpan di `android/app/build/outputs/apk/debug/app-debug.apk`
## Build APK Release (Signed)
Untuk production APK yang bisa di-upload ke Play Store:
1. Generate keystore:
```bash
keytool -genkey -v -keystore dms-sales.keystore -alias dms-sales -keyalg RSA -keysize 2048 -validity 10000
```
2. Edit `android/app/build.gradle`, tambahkan:
```gradle
android {
...
signingConfigs {
release {
storeFile file('../../dms-sales.keystore')
storePassword 'your-password'
keyAlias 'dms-sales'
keyPassword 'your-password'
}
}
buildTypes {
release {
signingConfig signingConfigs.release
...
}
}
}
```
3. Build release APK:
```bash
cd android
./gradlew assembleRelease
```
APK release tersimpan di `android/app/build/outputs/apk/release/app-release.apk`
## Troubleshooting
### Error: ANDROID_HOME not set
Set environment variable ANDROID_HOME ke lokasi Android SDK:
```bash
# Windows
set ANDROID_HOME=C:\Users\YourName\AppData\Local\Android\Sdk
# Linux/Mac
export ANDROID_HOME=$HOME/Android/Sdk
```
### Error: Java version
Pastikan menggunakan JDK 11 atau lebih baru:
```bash
java -version
```
### Error: Gradle sync failed
Buka Android Studio dan sync manual, atau jalankan:
```bash
cd android
./gradlew clean
```
## Testing APK
Install APK ke device/emulator:
```bash
adb install android/app/build/outputs/apk/debug/app-debug.apk
```
Atau transfer file APK ke device dan install manual.
# Cara Install Android SDK dan Build APK
## Opsi 1: Menggunakan Android Studio (Recommended)
### 1. Download dan Install Android Studio
- Download dari: https://developer.android.com/studio
- Install Android Studio
- Saat pertama kali buka, ikuti wizard setup untuk install Android SDK
### 2. Konfigurasi SDK
Setelah Android Studio terinstall, SDK biasanya ada di:
- Windows: `C:\Users\[USERNAME]\AppData\Local\Android\Sdk`
- Mac: `~/Library/Android/sdk`
- Linux: `~/Android/Sdk`
### 3. Set Environment Variable (Optional tapi recommended)
Windows:
```
setx ANDROID_HOME "C:\Users\[USERNAME]\AppData\Local\Android\Sdk"
setx PATH "%PATH%;%ANDROID_HOME%\platform-tools;%ANDROID_HOME%\tools"
```
### 4. Build APK via Android Studio
```bash
# Buka project di Android Studio
npx cap open android
# Di Android Studio:
# Build > Build Bundle(s) / APK(s) > Build APK(s)
```
APK akan tersimpan di: `android/app/build/outputs/apk/debug/app-debug.apk`
### 5. Build APK via Command Line
Setelah Android Studio terinstall:
```bash
cd dms-sales-app/android
./gradlew assembleDebug
```
---
## Opsi 2: Install Command Line Tools Only (Tanpa Android Studio)
### 1. Download Command Line Tools
- Download dari: https://developer.android.com/studio#command-tools
- Extract ke folder, misalnya: `C:\Android\cmdline-tools`
### 2. Install SDK Components
```bash
cd C:\Android\cmdline-tools\bin
sdkmanager "platform-tools" "platforms;android-33" "build-tools;33.0.0"
```
### 3. Buat local.properties
Buat file `dms-sales-app/android/local.properties`:
```
sdk.dir=C:\\Android
```
### 4. Build APK
```bash
cd dms-sales-app/android
./gradlew assembleDebug
```
---
## Opsi 3: Build Online (Tanpa Install SDK)
Gunakan layanan online seperti:
- **Expo EAS Build** (untuk React Native/Capacitor)
- **Appetize.io** (untuk testing)
- **GitHub Actions** (CI/CD dengan Android SDK pre-installed)
---
## Troubleshooting
### Error: SDK location not found
Buat file `android/local.properties`:
```
sdk.dir=C:\\Users\\[USERNAME]\\AppData\\Local\\Android\\Sdk
```
### Error: Java version
Pastikan Java 11 atau lebih baru terinstall:
```bash
java -version
```
Download JDK dari: https://adoptium.net/
### Error: Gradle build failed
Clean dan rebuild:
```bash
cd android
./gradlew clean
./gradlew assembleDebug
```
---
## Quick Start (Jika SDK Sudah Terinstall)
```bash
# 1. Build React app
npm run build
# 2. Sync dengan Capacitor
npx cap sync android
# 3. Build APK
cd android
./gradlew assembleDebug
# APK location:
# android/app/build/outputs/apk/debug/app-debug.apk
```
---
## Install APK ke Device
### Via USB:
```bash
adb install android/app/build/outputs/apk/debug/app-debug.apk
```
### Via File Transfer:
1. Copy file `app-debug.apk` ke device
2. Buka file di device
3. Allow "Install from Unknown Sources" jika diminta
4. Install aplikasi
# Panduan Cepat Build APK - DMS Sales App
## Status Saat Ini ✅
Aplikasi sudah siap dan berhasil di-build! Yang perlu dilakukan:
1. ✅ React app sudah dibuat
2. ✅ Tailwind CSS sudah dikonfigurasi
3. ✅ Capacitor sudah disetup
4. ✅ Build production sudah berhasil (`npm run build`)
5. ✅ Sync dengan Android sudah berhasil (`npx cap sync android`)
6. ⏳ Tinggal build APK (butuh Android SDK)
## Cara Tercepat Build APK
### Metode 1: Menggunakan Android Studio (Paling Mudah)
1. **Install Android Studio**
- Download: https://developer.android.com/studio
- Install dengan default settings (akan otomatis install Android SDK)
2. **Buka Project**
```bash
npx cap open android
```
3. **Build APK di Android Studio**
- Tunggu Gradle sync selesai
- Klik menu: **Build > Build Bundle(s) / APK(s) > Build APK(s)**
- Tunggu proses build (5-10 menit pertama kali)
- Klik **locate** untuk buka folder APK
4. **Lokasi APK**
```
dms-sales-app/android/app/build/outputs/apk/debug/app-debug.apk
```
### Metode 2: Command Line (Setelah Android Studio Terinstall)
```bash
# Dari folder dms-sales-app
cd android
./gradlew assembleDebug
```
APK akan ada di: `android/app/build/outputs/apk/debug/app-debug.apk`
## Install APK ke HP Android
### Cara 1: Via USB
```bash
adb install android/app/build/outputs/apk/debug/app-debug.apk
```
### Cara 2: Transfer File
1. Copy file `app-debug.apk` ke HP (via USB, email, atau cloud)
2. Buka file di HP
3. Izinkan "Install from Unknown Sources" jika diminta
4. Tap Install
## Ukuran APK yang Diharapkan
- Debug APK: ~7-10 MB
- Release APK (signed): ~5-7 MB
## Testing Aplikasi Tanpa Build APK
Jika ingin test dulu sebelum build APK:
```bash
# Test di browser
npm start
```
Buka http://localhost:3000 di browser
## Catatan Penting
- APK debug hanya untuk testing, tidak bisa di-upload ke Play Store
- Untuk production, perlu build release APK dengan signing key
- Aplikasi ini pure prototyping, tidak ada koneksi API/backend
## Butuh Bantuan?
Jika ada error saat build, cek file `INSTALL_ANDROID_SDK.md` untuk troubleshooting lengkap.
# DMS Sales App
Aplikasi prototyping untuk Dealer Management System (DMS) Sales berbasis React JS dengan Tailwind CSS.
## Fitur Aplikasi
1. **Beranda** - Dashboard dengan detail mobil dan simulasi kredit
2. **Prospek** - Daftar dan input data prospek pelanggan
3. **Penawaran** - Detail penawaran harga dan finalisasi
4. **Transaksi** - Riwayat transaksi penjualan
## Cara Menjalankan Aplikasi
### Development Mode
```bash
npm start
```
Aplikasi akan berjalan di `http://localhost:3000`
### Build untuk Production
```bash
npm run build
```
### Build APK Android
1. Build aplikasi React terlebih dahulu:
```bash
npm run build
```
2. Sync dengan Capacitor:
```bash
npx cap sync android
```
3. Buka project Android di Android Studio:
```bash
npx cap open android
```
4. Di Android Studio:
- Pastikan Android SDK sudah terinstall
- Pilih menu **Build > Build Bundle(s) / APK(s) > Build APK(s)**
- APK akan tersimpan di `android/app/build/outputs/apk/debug/app-debug.apk`
## Persyaratan untuk Build APK
- Node.js (v14 atau lebih baru)
- Android Studio
- Java Development Kit (JDK) 11 atau lebih baru
- Android SDK
## Teknologi yang Digunakan
- React JS
- React Router DOM
- Tailwind CSS
- Capacitor (untuk build APK)
## Catatan
Aplikasi ini adalah prototyping tanpa integrasi API. Semua data bersifat statis untuk keperluan demonstrasi.
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
# Built application files
*.apk
*.aar
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
# Android Profiling
*.hprof
# Cordova plugins for Capacitor
capacitor-cordova-android-plugins
# Copied web assets
app/src/main/assets/public
# Generated Config files
app/src/main/assets/capacitor.config.json
app/src/main/assets/capacitor.plugins.json
app/src/main/res/xml/config.xml
/build/*
!/build/.npmkeep
apply plugin: 'com.android.application'
android {
namespace "com.dms.sales"
compileSdk rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "com.dms.sales"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
repositories {
flatDir{
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
implementation project(':capacitor-android')
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
implementation project(':capacitor-cordova-android-plugins')
}
apply from: 'capacitor.build.gradle'
try {
def servicesJSON = file('google-services.json')
if (servicesJSON.text) {
apply plugin: 'com.google.gms.google-services'
}
} catch(Exception e) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
}
if (hasProperty('postBuildExtras')) {
postBuildExtras()
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
package com.getcapacitor.myapp;
import static org.junit.Assert.*;
import android.content.Context;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.getcapacitor.app", appContext.getPackageName());
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
android:name=".MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"></meta-data>
</provider>
</application>
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
package com.dms.sales;
import com.getcapacitor.BridgeActivity;
public class MainActivity extends BridgeActivity {}
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#26A69A"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>
\ No newline at end of file
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="app_name">DMS Sales</string>
<string name="title_activity_main">DMS Sales</string>
<string name="package_name">com.dms.sales</string>
<string name="custom_url_scheme">com.dms.sales</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:background">@null</item>
</style>
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<item name="android:background">@drawable/splash</item>
</style>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="." />
<cache-path name="my_cache_images" path="." />
</paths>
\ No newline at end of file
package com.getcapacitor.myapp;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.2.1'
classpath 'com.google.gms:google-services:4.4.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
apply from: "variables.gradle"
allprojects {
repositories {
google()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
include ':app'
include ':capacitor-cordova-android-plugins'
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
apply from: 'capacitor.settings.gradle'
\ No newline at end of file
ext {
minSdkVersion = 22
compileSdkVersion = 34
targetSdkVersion = 34
androidxActivityVersion = '1.8.0'
androidxAppCompatVersion = '1.6.1'
androidxCoordinatorLayoutVersion = '1.2.0'
androidxCoreVersion = '1.12.0'
androidxFragmentVersion = '1.6.2'
coreSplashScreenVersion = '1.0.1'
androidxWebkitVersion = '1.9.0'
junitVersion = '4.13.2'
androidxJunitVersion = '1.1.5'
androidxEspressoCoreVersion = '3.5.1'
cordovaAndroidVersion = '10.1.1'
}
\ No newline at end of file
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.dms.sales',
appName: 'DMS Sales',
webDir: 'build'
};
export default config;
App/build
App/Pods
App/output
App/App/public
DerivedData
xcuserdata
# Cordova plugins for Capacitor
capacitor-cordova-ios-plugins
# Generated Config files
App/App/capacitor.config.json
App/App/config.xml
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
import UIKit
import Capacitor
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
// Called when the app was launched with a url. Feel free to add additional processing here,
// but if you want the App API to support tracking app url opens, make sure to keep this call
return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// Called when the app was launched with an activity, including Universal Links.
// Feel free to add additional processing here, but if you want the App API to support
// tracking app url opens, make sure to keep this call
return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
}
{
"images" : [
{
"filename" : "AppIcon-512@2x.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
{
"images" : [
{
"idiom" : "universal",
"filename" : "splash-2732x2732-2.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "splash-2732x2732-1.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "splash-2732x2732.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17132" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17105"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<imageView key="view" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Splash" id="snD-IY-ifK">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</imageView>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="Splash" width="1366" height="1366"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14111" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
</dependencies>
<scenes>
<!--Bridge View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="CAPBridgeViewController" customModule="Capacitor" sceneMemberID="viewController"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>DMS Sales</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
</dict>
</plist>
require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers'
platform :ios, '13.0'
use_frameworks!
# workaround to avoid Xcode caching of Pods that requires
# Product -> Clean Build Folder after new Cordova plugins installed
# Requires CocoaPods 1.6 or newer
install! 'cocoapods', :disable_input_output_paths => true
def capacitor_pods
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
end
target 'App' do
capacitor_pods
# Add your Pods here
end
post_install do |installer|
assertDeploymentTarget(installer)
end
This diff is collapsed.
{
"name": "dms-sales-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@capacitor/android": "^6.1.2",
"@capacitor/cli": "^6.1.2",
"@capacitor/core": "^6.1.2",
"@capacitor/ios": "^6.2.1",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^13.5.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router-dom": "^7.14.0",
"react-scripts": "^5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"autoprefixer": "^10.4.17",
"postcss": "^8.4.35",
"tailwindcss": "^3.4.1"
}
}
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
import React, { useState, useEffect } from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import Login from './pages/Login';
import Beranda from './pages/Beranda';
import Prospek from './pages/Prospek';
import InputProspek from './pages/InputProspek';
import Penawaran from './pages/Penawaran';
import Activities from './pages/Activities';
import Transaksi from './pages/Transaksi';
import DetailAktivitas from './pages/DetailAktivitas';
import DetailSPK from './pages/DetailSPK';
import FollowUp from './pages/FollowUp';
import AskDora from './pages/AskDora';
import KonfirmasiDeal from './pages/KonfirmasiDeal';
import BuatPenawaran from './pages/BuatPenawaran';
import Tracking from './pages/Tracking';
import DetailMonitoring from './pages/DetailMonitoring';
import Profil from './pages/Profil';
function App() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
useEffect(() => {
const loggedIn = localStorage.getItem('isLoggedIn') === 'true';
setIsLoggedIn(loggedIn);
}, []);
const ProtectedRoute = ({ children }) => {
return isLoggedIn ? children : <Navigate to="/login" />;
};
return (
<Router>
<div className="min-h-screen bg-gray-50">
<Routes>
<Route path="/login" element={isLoggedIn ? <Navigate to="/" /> : <Login />} />
<Route path="/" element={<ProtectedRoute><Beranda /></ProtectedRoute>} />
<Route path="/prospek" element={<ProtectedRoute><Prospek /></ProtectedRoute>} />
<Route path="/prospek/input" element={<ProtectedRoute><InputProspek /></ProtectedRoute>} />
<Route path="/penawaran" element={<ProtectedRoute><Penawaran /></ProtectedRoute>} />
<Route path="/activities" element={<ProtectedRoute><Activities /></ProtectedRoute>} />
<Route path="/transaksi" element={<ProtectedRoute><Transaksi /></ProtectedRoute>} />
<Route path="/detail-aktivitas" element={<ProtectedRoute><DetailAktivitas /></ProtectedRoute>} />
<Route path="/detail-spk" element={<ProtectedRoute><DetailSPK /></ProtectedRoute>} />
<Route path="/follow-up" element={<ProtectedRoute><FollowUp /></ProtectedRoute>} />
<Route path="/ask-dora" element={<ProtectedRoute><AskDora /></ProtectedRoute>} />
<Route path="/konfirmasi-deal" element={<ProtectedRoute><KonfirmasiDeal /></ProtectedRoute>} />
<Route path="/buat-penawaran" element={<ProtectedRoute><BuatPenawaran /></ProtectedRoute>} />
<Route path="/tracking" element={<ProtectedRoute><Tracking /></ProtectedRoute>} />
<Route path="/detail-monitoring" element={<ProtectedRoute><DetailMonitoring /></ProtectedRoute>} />
<Route path="/profil" element={<ProtectedRoute><Profil /></ProtectedRoute>} />
</Routes>
</div>
</Router>
);
}
export default App;
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
# Build Commands - DMS Sales App
## Quick Build Commands
### Android APK
#### Debug APK (untuk testing)
```bash
# 1. Build React app
npm run build
# 2. Sync to Android
npx cap sync android
# 3. Build APK
cd android
gradlew assembleDebug
cd ..
```
APK location: `android/app/build/outputs/apk/debug/app-debug.apk`
#### Release APK (untuk production)
```bash
# 1. Build React app
npm run build
# 2. Sync to Android
npx cap sync android
# 3. Build Release APK
cd android
gradlew assembleRelease
cd ..
```
APK location: `android/app/build/outputs/apk/release/app-release.apk`
### iOS App
#### Development Build
```bash
# 1. Build React app
npm run build
# 2. Add iOS platform (first time only)
npx cap add ios
# 3. Sync to iOS
npx cap sync ios
# 4. Open in Xcode
npx cap open ios
```
Then in Xcode: Product > Run (⌘R)
#### Production Build
```bash
# 1. Build React app
npm run build
# 2. Sync to iOS
npx cap sync ios
# 3. Open in Xcode
npx cap open ios
```
Then in Xcode: Product > Archive
## One-Line Commands
### Android Debug
```bash
npm run build && npx cap sync android && cd android && gradlew assembleDebug && cd ..
```
### Android Release
```bash
npm run build && npx cap sync android && cd android && gradlew assembleRelease && cd ..
```
### iOS
```bash
npm run build && npx cap sync ios && npx cap open ios
```
## PowerShell Commands (Windows)
### Android Debug
```powershell
npm run build; npx cap sync android; Push-Location android; .\gradlew.bat assembleDebug; Pop-Location
```
### Android Release
```powershell
npm run build; npx cap sync android; Push-Location android; .\gradlew.bat assembleRelease; Pop-Location
```
### Open Android Studio
```powershell
npx cap open android
```
## Bash Commands (Mac/Linux)
### Android Debug
```bash
npm run build && npx cap sync android && cd android && ./gradlew assembleDebug && cd ..
```
### Android Release
```bash
npm run build && npx cap sync android && cd android && ./gradlew assembleRelease && cd ..
```
### iOS
```bash
npm run build && npx cap sync ios && npx cap open ios
```
## Build Status
**Android Debug APK** - Successfully built!
- Location: `android/app/build/outputs/apk/debug/app-debug.apk`
- Size: ~4.3 MB
- Ready for testing on Android devices
**iOS App** - Requires macOS with Xcode
- Follow iOS build instructions in BUILD_IOS.md
- Requires Apple Developer Account for device testing
## Testing
### Install APK on Android Device
#### Via USB (ADB)
```bash
adb install android/app/build/outputs/apk/debug/app-debug.apk
```
#### Via File Transfer
1. Copy APK to device
2. Open file manager on device
3. Tap APK file
4. Allow installation from unknown sources if prompted
5. Install
### Test on iOS Device
#### Via Xcode
1. Connect iPhone/iPad via USB
2. Open project in Xcode
3. Select your device
4. Click Run (⌘R)
#### Via TestFlight
1. Archive and upload to App Store Connect
2. Add testers in TestFlight
3. Testers install via TestFlight app
## Clean Build
If you encounter issues, clean and rebuild:
### Android
```bash
cd android
gradlew clean
gradlew assembleDebug
cd ..
```
### iOS
```bash
cd ios/App
pod deintegrate
pod install
cd ../..
npx cap sync ios
```
## Update Native Code
After adding new Capacitor plugins:
```bash
npm install
npx cap sync
```
## Live Reload (Development)
For faster development with live reload:
```bash
# Terminal 1: Start React dev server
npm start
# Terminal 2: Run on Android with live reload
npx cap run android -l --external
# Or for iOS
npx cap run ios -l --external
```
## Production Checklist
Before building for production:
- [ ] Update version in `package.json`
- [ ] Update version code in `android/app/build.gradle`
- [ ] Update version in `ios/App/App.xcodeproj`
- [ ] Test all features
- [ ] Optimize images and assets
- [ ] Remove console.logs
- [ ] Update app icons and splash screens
- [ ] Test on multiple devices
- [ ] Create release notes
## Troubleshooting
### Gradle build fails
```bash
cd android
gradlew clean
cd ..
npm run build
npx cap sync android
cd android
gradlew assembleDebug
```
### iOS build fails
```bash
cd ios/App
pod repo update
pod install
cd ../..
npx cap sync ios
```
### Web assets not updating
```bash
npm run build
npx cap copy
```
## Resources
- [Capacitor Documentation](https://capacitorjs.com/docs)
- [Android Studio](https://developer.android.com/studio)
- [Xcode](https://developer.apple.com/xcode/)
- [Gradle Documentation](https://gradle.org/guides/)
# Panduan Build iOS App
## Persyaratan
- macOS (iOS development hanya bisa di Mac)
- Xcode 14 atau lebih baru
- CocoaPods
- Apple Developer Account (untuk deploy ke device/App Store)
## Langkah-langkah Build iOS
### 1. Install Dependencies
```bash
npm install
```
### 2. Build Aplikasi React
```bash
npm run build
```
### 3. Tambah iOS Platform (jika belum)
```bash
npx cap add ios
```
### 4. Sync dengan Capacitor
```bash
npx cap sync ios
```
### 5. Buka di Xcode
```bash
npx cap open ios
```
### 6. Build di Xcode
Setelah Xcode terbuka:
1. Pilih target device/simulator di toolbar atas
2. Pilih menu **Product > Build** (⌘B)
3. Untuk run di simulator: **Product > Run** (⌘R)
4. Untuk archive: **Product > Archive**
## Build untuk Testing (Development)
### Run di Simulator
1. Buka Xcode
2. Pilih simulator (iPhone 14, iPad, dll)
3. Klik tombol Play atau tekan ⌘R
### Run di Device Fisik
1. Hubungkan iPhone/iPad via USB
2. Trust device di Mac dan iPhone
3. Di Xcode, pilih device Anda
4. Pilih **Signing & Capabilities** tab
5. Pilih Team (Apple Developer Account)
6. Klik Play untuk install dan run
## Build untuk Production (App Store)
### 1. Setup Signing
1. Buka project di Xcode
2. Pilih target **App**
3. Tab **Signing & Capabilities**
4. Pilih Team (Apple Developer Account)
5. Pastikan Bundle Identifier unik: `com.dms.sales`
### 2. Archive Build
```bash
# Di Xcode:
# Product > Archive
```
Atau via command line:
```bash
cd ios/App
xcodebuild -workspace App.xcworkspace \
-scheme App \
-configuration Release \
-archivePath ./build/App.xcarchive \
archive
```
### 3. Export IPA
1. Setelah archive selesai, Organizer akan terbuka
2. Pilih archive terbaru
3. Klik **Distribute App**
4. Pilih metode distribusi:
- **App Store Connect** - untuk upload ke App Store
- **Ad Hoc** - untuk testing internal
- **Development** - untuk testing developer
- **Enterprise** - untuk distribusi internal perusahaan
### 4. Upload ke App Store Connect
1. Pilih **App Store Connect**
2. Klik **Upload**
3. Tunggu proses upload selesai
4. Buka App Store Connect di browser
5. Buat app baru atau pilih app yang ada
6. Submit untuk review
## Testing IPA
### Install via TestFlight
1. Upload IPA ke App Store Connect
2. Tambahkan tester di TestFlight
3. Tester akan menerima email invite
4. Install TestFlight app dari App Store
5. Install app dari TestFlight
### Install via Xcode
```bash
# Connect device
# Drag IPA file ke Xcode Devices window
```
## Update Capacitor Config untuk iOS
Edit `capacitor.config.ts`:
```typescript
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.dms.sales',
appName: 'DMS Sales',
webDir: 'build',
ios: {
contentInset: 'always',
scrollEnabled: true
}
};
export default config;
```
## Troubleshooting
### Error: CocoaPods not installed
```bash
sudo gem install cocoapods
```
### Error: Pod install failed
```bash
cd ios/App
pod repo update
pod install
```
### Error: Signing failed
1. Pastikan Apple Developer Account aktif
2. Buat App ID di developer.apple.com
3. Buat Provisioning Profile
4. Download dan install di Xcode
### Error: Build failed - Swift version
1. Buka Xcode
2. Build Settings > Swift Language Version
3. Set ke Swift 5.0 atau lebih baru
### Error: Device not trusted
1. Di iPhone: Settings > General > Device Management
2. Trust developer certificate
## App Icons & Splash Screen
### Generate Icons
1. Buat icon 1024x1024px
2. Gunakan tool online atau:
```bash
npm install -g @capacitor/assets
npx capacitor-assets generate --ios
```
### Custom Splash Screen
1. Edit `ios/App/App/Assets.xcassets/Splash.imageset/`
2. Atau gunakan Capacitor Splash Screen plugin
## Optimasi Build
### Reduce App Size
1. Enable bitcode (jika memungkinkan)
2. Strip debug symbols untuk release
3. Optimize images dan assets
### Performance
1. Enable WKWebView optimizations
2. Use native navigation jika perlu
3. Lazy load components
## Resources
- [Capacitor iOS Documentation](https://capacitorjs.com/docs/ios)
- [Apple Developer Documentation](https://developer.apple.com/documentation/)
- [App Store Review Guidelines](https://developer.apple.com/app-store/review/guidelines/)
- [TestFlight Documentation](https://developer.apple.com/testflight/)
# 🚀 Build Status - DMS Sales App
## ✅ Build Completed Successfully!
---
## 📱 Android APK
### Status: ✅ READY TO INSTALL
**APK Information:**
- **Location:** `android/app/build/outputs/apk/debug/app-debug.apk`
- **File Size:** 4.12 MB
- **Build Time:** 09 April 2026, 19:21:05
- **Build Type:** Debug (for testing)
- **Min SDK:** Android 5.0 (API 21)
- **Target SDK:** Android 14 (API 34)
### 📥 How to Install APK
#### Method 1: Via USB (ADB)
```bash
# Connect Android device via USB
# Enable USB Debugging on device
adb install android/app/build/outputs/apk/debug/app-debug.apk
```
#### Method 2: Direct Transfer
1. Copy `app-debug.apk` to your Android device
2. Open file manager on device
3. Tap the APK file
4. Allow installation from unknown sources (if prompted)
5. Tap "Install"
#### Method 3: Via Email/Cloud
1. Email the APK to yourself or upload to cloud storage
2. Download on Android device
3. Open and install
### 🔄 Rebuild Android APK
If you make changes and need to rebuild:
```bash
# 1. Build React app
npm run build
# 2. Sync to Android
npx cap sync android
# 3. Build APK
cd android
gradlew assembleDebug
cd ..
```
**One-line command (PowerShell):**
```powershell
npm run build; npx cap sync android; Push-Location android; .\gradlew.bat assembleDebug; Pop-Location
```
---
## 🍎 iOS App
### Status: ⚠️ REQUIRES macOS
**Platform Information:**
- **Location:** `ios/App/App.xcworkspace`
- **Status:** Project created, ready for Xcode
- **Requirement:** macOS with Xcode installed
- **iOS Version:** iOS 13.0+
### 📋 Prerequisites for iOS Build
1. **macOS Computer** (iOS development only works on Mac)
2. **Xcode 14+** - Download from Mac App Store
3. **CocoaPods** - Install via:
```bash
sudo gem install cocoapods
```
4. **Apple Developer Account** (for device testing/App Store)
### 🛠️ Build iOS App (on Mac)
#### Step 1: Install Dependencies
```bash
cd ios/App
pod install
cd ../..
```
#### Step 2: Open in Xcode
```bash
npx cap open ios
```
#### Step 3: Build in Xcode
1. Select target device/simulator
2. Click Play button (⌘R) to run
3. Or: Product > Build (⌘B)
#### Step 4: Archive for Distribution
1. Product > Archive
2. Wait for archive to complete
3. Organizer window will open
4. Select archive and click "Distribute App"
### 📱 Testing on iOS
#### Simulator (Free)
```bash
# Open in Xcode
npx cap open ios
# Select simulator (iPhone 14, iPad, etc.)
# Click Play button
```
#### Physical Device (Requires Apple Developer Account)
1. Connect iPhone/iPad via USB
2. Trust device on Mac and iPhone
3. In Xcode: Select your device
4. Signing & Capabilities > Select Team
5. Click Play to install and run
### 🔄 Rebuild iOS App
```bash
# 1. Build React app
npm run build
# 2. Sync to iOS
npx cap sync ios
# 3. Open in Xcode
npx cap open ios
# Then build in Xcode
```
---
## 📊 Build Summary
| Platform | Status | Size | Location |
|----------|--------|------|----------|
| Android APK | ✅ Ready | 4.12 MB | `android/app/build/outputs/apk/debug/` |
| iOS App | ⚠️ Needs Mac | N/A | `ios/App/App.xcworkspace` |
---
## 🎯 Features Included
✅ Login with authentication
✅ Dashboard with statistics
✅ Prospect management with filters
✅ Add new prospect form
✅ Activity details tracking
✅ AI Assistant (DORA) chat
✅ Deal confirmation
✅ Quotation builder
✅ User profile management
✅ Sticky headers on all pages
✅ Scrollable content
✅ Bottom navigation
✅ Responsive design
---
## 🔧 Technical Details
### React Build
- **Status:** ✅ Compiled successfully
- **Bundle Size:** 87.85 kB (gzipped)
- **CSS Size:** 4.02 kB (gzipped)
- **Warnings:** None
### Capacitor
- **Version:** 6.1.2
- **Android:** ✅ Configured
- **iOS:** ✅ Configured
- **Web Dir:** build
### Dependencies
- React: 19.2.4
- React Router: 7.14.0
- Tailwind CSS: 3.4.1
- Capacitor Android: 6.1.2
- Capacitor iOS: 6.1.2
---
## 📝 Next Steps
### For Android Testing:
1. ✅ APK is ready - install on device
2. Test all features
3. Report any bugs
4. For production: Build release APK with signing
### For iOS Testing:
1. ⚠️ Need macOS with Xcode
2. Run `pod install` in ios/App directory
3. Open in Xcode
4. Test on simulator or device
5. For production: Archive and submit to App Store
---
## 🐛 Troubleshooting
### Android APK Not Installing
- Enable "Install from Unknown Sources"
- Check device storage space
- Try uninstalling old version first
### iOS Build Fails
- Update Xcode to latest version
- Run `pod repo update` and `pod install`
- Clean build folder: Product > Clean Build Folder
- Check signing certificates
### App Crashes on Launch
- Check device logs
- Verify minimum OS version
- Rebuild with latest code
---
## 📚 Documentation
- **Build Commands:** See `BUILD_COMMANDS.md`
- **Android Guide:** See `BUILD_APK.md`
- **iOS Guide:** See `BUILD_IOS.md`
- **Changelog:** See `CHANGELOG.md`
---
## 🎉 Success!
Your DMS Sales App is now built and ready for testing!
**Android:** Install the APK and start testing
**iOS:** Open in Xcode on Mac to build and test
For any issues, check the troubleshooting section or documentation files.
---
**Build Date:** 09 April 2026
**Version:** 0.1.0
**Build Type:** Development/Debug
# Changelog - DMS Sales App
## [Latest Update] - Sticky Header & Scrollable Content
### ✨ Fitur Baru
- **Sticky Header**: Semua header di setiap halaman sekarang sticky (tetap di atas saat scroll)
- **Scrollable Content**: Konten halaman dapat di-scroll dengan smooth
- **Better UX**: Navigasi lebih mudah dengan header yang selalu terlihat
### 📱 Halaman yang Diupdate
#### Dengan Sticky Header:
1.**Beranda** - Header sticky, konten scrollable
2.**Manajemen Prospek** - Header sticky, konten scrollable
3.**Input Prospek** - Custom header sticky, konten scrollable
4.**Detail Aktivitas** - Header sticky, konten scrollable
5.**Ask Dora** - Header sticky, chat scrollable, input sticky di bawah
6.**Konfirmasi Deal** - Header sticky, form scrollable
7.**Buat Penawaran** - Header sticky, form scrollable
8.**Profil** - Header sticky, menu scrollable
9.**Penawaran** - Header sticky, konten scrollable
10.**Transaksi** - Header sticky, list scrollable
### 🎨 Perubahan Teknis
#### Layout Structure
```jsx
// Before
<div className="pb-20 bg-gray-50 min-h-screen">
<Header />
<div className="p-4">
{/* Content */}
</div>
<BottomNav />
</div>
// After
<div className="flex flex-col h-screen bg-gray-50">
<Header /> {/* Sticky at top */}
<div className="flex-1 overflow-y-auto pb-20">
<div className="p-4">
{/* Scrollable Content */}
</div>
</div>
<BottomNav /> {/* Fixed at bottom */}
</div>
```
#### Header Component
```jsx
// Updated to sticky
<div className="sticky top-0 z-50 bg-white px-4 py-3 flex items-center justify-between shadow-sm">
```
#### Special Cases
**Ask Dora Page:**
- Header: Sticky at top
- Content: Scrollable in middle
- Input Area: Sticky at bottom
```jsx
<div className="flex flex-col h-screen">
<div className="sticky top-0 z-50">Header</div>
<div className="flex-1 overflow-y-auto">Content</div>
<div className="sticky bottom-0">Input</div>
</div>
```
### 🐛 Bug Fixes
- Removed unused imports (navigate, Header, state variables)
- Fixed ESLint warnings
- Cleaned up code structure
### 📦 Build Info
- **Build Status**: ✅ Compiled successfully
- **Bundle Size**: 87.85 kB (gzipped)
- **CSS Size**: 4.02 kB (gzipped)
- **No Warnings**: Clean build
### 🚀 Deployment
```bash
# Build React app
npm run build
# Sync to Android
npx cap sync android
# Build APK (optional)
cd android
gradlew assembleDebug
```
### 📱 Testing Checklist
- [ ] Test scroll behavior on all pages
- [ ] Verify header stays at top when scrolling
- [ ] Check bottom navigation is always visible
- [ ] Test on different screen sizes
- [ ] Verify floating buttons (Ask Dora, Add Lead) work correctly
- [ ] Test on Android device/emulator
- [ ] Test on iOS device/simulator (if available)
### 💡 Benefits
1. **Better Navigation**: Header always accessible
2. **More Content Space**: Full screen height utilized
3. **Improved UX**: Natural scrolling behavior
4. **Mobile-First**: Optimized for mobile devices
5. **Consistent**: Same behavior across all pages
### 🔄 Migration Notes
If you're updating from previous version:
1. Pull latest changes
2. Run `npm install` (if needed)
3. Run `npm run build`
4. Run `npx cap sync`
5. Test on device
### 📝 Next Steps
- [ ] Add pull-to-refresh functionality
- [ ] Implement infinite scroll for lists
- [ ] Add loading skeletons
- [ ] Optimize scroll performance
- [ ] Add scroll-to-top button
---
## Previous Updates
### Initial Release
- Login page with authentication
- Dashboard with statistics
- Prospect management
- Activity tracking
- AI assistant (DORA)
- Deal confirmation
- Quotation builder
- Profile management
- Bottom navigation
- Responsive design
# Update Notes - Ask Dora Integration
## 🆕 Latest Update
### ✨ Ask Dora Button Added to More Pages
**Date:** 09 April 2026
---
## 📱 Changes Made
### 1. Ask Dora Button on Activities Page (Penawaran)
- **Location:** Bottom left corner
- **Style:** Floating button with sticky position
- **Action:** Opens Ask Dora chat interface
- **Z-index:** 10 (above content, below bottom nav)
```jsx
// Added to pages/Penawaran.js
<button
onClick={() => navigate('/ask-dora')}
className="fixed bottom-24 left-4 bg-slate-800 text-white px-4 py-3 rounded-full shadow-lg flex items-center gap-2 z-10"
>
<svg>...</svg>
<span>ASK DORA</span>
</button>
```
### 2. Ask Dora Button on Quotation Page (Transaksi)
- **Location:** Bottom left corner
- **Style:** Floating button with sticky position
- **Action:** Opens Ask Dora chat interface
- **Z-index:** 10 (above content, below bottom nav)
```jsx
// Added to pages/Transaksi.js
<button
onClick={() => navigate('/ask-dora')}
className="fixed bottom-24 left-4 bg-slate-800 text-white px-4 py-3 rounded-full shadow-lg flex items-center gap-2 z-10"
>
<svg>...</svg>
<span>ASK DORA</span>
</button>
```
### 3. Ask Dora Input Area - Enhanced Sticky
- **Location:** Bottom of Ask Dora page
- **Style:** Sticky at bottom with proper z-index
- **Features:**
- Input field for typing messages
- Send button
- Add attachment button
- Always visible while scrolling
```jsx
// Updated in pages/AskDora.js
<div className="sticky bottom-0 z-40 bg-white border-t border-gray-200 p-4 pb-safe">
<div className="flex items-center gap-2">
<button>+</button>
<input placeholder="Tulis pesan..." />
<button>Send</button>
</div>
</div>
```
### 4. Bottom Navigation - Z-Index Update
- **Z-index:** 50 (highest priority)
- **Purpose:** Ensure bottom nav is always on top
- **Safe area:** Added safe-area-inset-bottom for notched devices
```jsx
// Updated in components/BottomNav.js
<div className="fixed bottom-0 left-0 right-0 z-50 bg-white border-t border-gray-200 px-2 py-2 safe-area-inset-bottom">
```
---
## 🎯 Pages with Ask Dora Button
| Page | Button Location | Status |
|------|----------------|--------|
| Beranda (Home) | Bottom left | ✅ Active |
| Prospek (Leads) | Bottom left | ✅ Active |
| Penawaran (Activities) | Bottom left | ✅ NEW |
| Transaksi (Quotation) | Bottom left | ✅ NEW |
| Ask Dora | N/A (is the page) | ✅ Active |
---
## 📐 Z-Index Hierarchy
```
Level 50: Bottom Navigation (highest)
Level 40: Ask Dora Input Area
Level 10: Floating Buttons (Ask Dora, Add Lead)
Level 0: Page Content
```
This ensures:
- Bottom nav is always accessible
- Ask Dora input doesn't hide behind bottom nav
- Floating buttons are above content but below navigation
---
## 🎨 Visual Design
### Ask Dora Button
- **Background:** Slate 800 (dark gray)
- **Text:** White
- **Icon:** Chat bubble
- **Shape:** Rounded full (pill shape)
- **Shadow:** Large shadow for depth
- **Position:** Fixed at bottom-24 left-4
- **Size:** Compact but tappable
### Input Area (Ask Dora Page)
- **Background:** White
- **Border:** Top border only
- **Padding:** 4 units with safe area
- **Input:** Rounded full with gray background
- **Buttons:** Icon buttons for add and send
---
## 🔄 User Flow
### From Activities/Quotation to Ask Dora:
1. User is on Activities or Quotation page
2. Sees "ASK DORA" button at bottom left
3. Taps button
4. Navigates to Ask Dora chat interface
5. Can ask questions or get help
6. Can navigate back via back button or bottom nav
### Ask Dora Chat Experience:
1. Header is sticky at top (with back button)
2. Chat content scrolls in middle
3. Input area is sticky at bottom
4. Bottom navigation is always visible
5. User can type and send messages
6. Can scroll through chat history
---
## 🧪 Testing Checklist
- [x] Ask Dora button appears on Activities page
- [x] Ask Dora button appears on Quotation page
- [x] Button navigates to Ask Dora page
- [x] Input area is sticky on Ask Dora page
- [x] Input area doesn't hide behind bottom nav
- [x] Bottom nav is always on top
- [x] Scrolling works smoothly
- [x] Button is tappable and responsive
- [x] Z-index hierarchy is correct
- [x] Build compiles successfully
---
## 📦 Build Information
### React Build
- **Status:** ✅ Compiled successfully
- **Bundle Size:** 87.9 kB (gzipped)
- **CSS Size:** 4.03 kB (gzipped)
- **Warnings:** None
### Capacitor Sync
- **Android:** ✅ Synced
- **iOS:** ✅ Synced
- **Web:** ✅ Synced
---
## 🚀 Deployment
### Quick Deploy Commands:
```bash
# Build and sync
npm run build && npx cap sync
# Build Android APK
cd android
gradlew assembleDebug
cd ..
# Open iOS in Xcode (Mac only)
npx cap open ios
```
### PowerShell One-liner:
```powershell
npm run build; npx cap sync; Push-Location android; .\gradlew.bat assembleDebug; Pop-Location
```
---
## 💡 Benefits
1. **Better Accessibility:** Ask Dora available on more pages
2. **Consistent UX:** Same button style across all pages
3. **Quick Help:** Users can get assistance without navigating
4. **Improved Flow:** Sticky input makes chatting easier
5. **Professional Look:** Proper z-index hierarchy
---
## 🔮 Future Enhancements
- [ ] Add notification badge on Ask Dora button
- [ ] Implement real-time chat functionality
- [ ] Add typing indicators
- [ ] Voice input support
- [ ] Quick action buttons in chat
- [ ] Chat history persistence
- [ ] Offline message queue
---
## 📝 Notes
- Ask Dora button uses `navigate('/ask-dora')` for routing
- Input area uses `sticky bottom-0` for positioning
- Z-index values are carefully chosen to avoid conflicts
- Safe area insets ensure compatibility with notched devices
- All changes are responsive and mobile-optimized
---
**Updated by:** Kiro AI Assistant
**Date:** 09 April 2026
**Version:** 0.1.1
import React from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
const BottomNav = () => {
const navigate = useNavigate();
const location = useLocation();
const navItems = [
{ path: '/', label: 'Beranda', icon: 'home' },
{ path: '/prospek', label: 'Prospek', icon: 'users' },
{ path: '/activities', label: 'Penjualan', icon: 'file' },
{ path: '/tracking', label: 'Tracking', icon: 'repeat' },
];
return (
<div className="fixed bottom-0 left-0 right-0 z-50 bg-white border-t border-gray-200 px-2 py-2 safe-area-inset-bottom">
<div className="flex justify-around items-center">
{navItems.map((item) => (
<button
key={item.path}
onClick={() => navigate(item.path)}
className={`flex flex-col items-center gap-1 px-4 py-1 ${
location.pathname === item.path ? 'text-blue-600' : 'text-gray-400'
}`}
>
<div className="w-6 h-6">
{item.icon === 'home' && (
<svg fill="currentColor" viewBox="0 0 20 20">
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" />
</svg>
)}
{item.icon === 'users' && (
<svg fill="currentColor" viewBox="0 0 20 20">
<path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" />
</svg>
)}
{item.icon === 'file' && (
<svg fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clipRule="evenodd" />
</svg>
)}
{item.icon === 'repeat' && (
<svg fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" clipRule="evenodd" />
</svg>
)}
{item.icon === 'user' && (
<svg fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clipRule="evenodd" />
</svg>
)}
</div>
<span className="text-xs font-medium">{item.label}</span>
</button>
))}
</div>
</div>
);
};
export default BottomNav;
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
const Header = ({ title = 'DMS Sales' }) => {
const navigate = useNavigate();
const [showLogoutMenu, setShowLogoutMenu] = useState(false);
const handleLogout = () => {
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('rememberMe');
navigate('/login');
window.location.reload();
};
return (
<div className="sticky top-0 z-50 bg-white px-4 py-3 flex items-center justify-between shadow-sm">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-gray-800 rounded-full flex items-center justify-center">
<span className="text-white text-sm font-bold">D</span>
</div>
<span className="font-semibold text-gray-800">{title}</span>
</div>
<div className="relative">
<button
onClick={() => setShowLogoutMenu(!showLogoutMenu)}
className="text-gray-400 hover:text-gray-600"
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" />
</svg>
</button>
{showLogoutMenu && (
<>
<div
className="fixed inset-0 z-10"
onClick={() => setShowLogoutMenu(false)}
/>
<div className="absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg z-20 py-1">
<button
onClick={handleLogout}
className="w-full px-4 py-2 text-left text-red-600 hover:bg-gray-50 flex items-center gap-2"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
Logout
</button>
</div>
</>
)}
</div>
</div>
);
};
export default Header;
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@keyframes scale-in {
0% {
opacity: 0;
transform: scale(0.9);
}
100% {
opacity: 1;
transform: scale(1);
}
}
.animate-scale-in {
animation: scale-in 0.3s ease-out;
}
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
\ No newline at end of file
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import BottomNav from '../components/BottomNav';
const Activities = () => {
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState('semua');
const [searchQuery, setSearchQuery] = useState('');
const spkList = [
{
id: 1,
nomorDokumen: 'SPK/2024/08/0129',
pelanggan: 'Bambang Wijaya',
unitKendaraan: 'Chery Tiggo 8 Pro',
tanggal: '24 Ags 2024',
status: 'APPROVED',
statusColor: 'bg-green-100 text-green-700'
},
{
id: 2,
nomorDokumen: 'SPK/2024/08/0130',
pelanggan: 'Siti Maesaroh',
unitKendaraan: 'Omoda 5 GT AWD',
tanggal: '25 Ags 2024',
status: 'PENDING',
statusColor: 'bg-yellow-100 text-yellow-700'
},
{
id: 3,
nomorDokumen: 'SPK/2024/08/0131',
pelanggan: 'Hendri Kusuma',
unitKendaraan: 'Chery Omoda E5',
tanggal: '25 Ags 2024',
status: 'PROCESSING',
statusColor: 'bg-blue-100 text-blue-700'
}
];
const filteredSPK = spkList.filter(spk => {
const matchesSearch = spk.nomorDokumen.toLowerCase().includes(searchQuery.toLowerCase()) ||
spk.pelanggan.toLowerCase().includes(searchQuery.toLowerCase());
const matchesTab = activeTab === 'semua' ? true :
activeTab === 'menunggu' ? spk.status === 'PENDING' :
activeTab === 'disetujui' ? spk.status === 'APPROVED' : true;
return matchesSearch && matchesTab;
});
return (
<div className="flex flex-col h-screen bg-gray-50">
{/* Header */}
<div className="sticky top-0 z-50 bg-white px-4 py-3 shadow-sm">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<button className="text-gray-700">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
<h1 className="text-lg font-bold text-gray-900">Monitoring SPK</h1>
</div>
<button className="w-10 h-10 bg-teal-700 rounded-full flex items-center justify-center text-white font-bold">
D
</button>
</div>
{/* Search Bar */}
<div className="relative mb-4">
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Cari No. SPK atau Nama Pelanggan"
className="w-full pl-10 pr-4 py-3 bg-gray-100 border-0 rounded-xl text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-slate-800"
/>
<svg className="w-5 h-5 text-gray-400 absolute left-3 top-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
{/* Tabs */}
<div className="flex gap-2 overflow-x-auto pb-2">
{[
{ id: 'semua', label: 'Semua' },
{ id: 'menunggu', label: 'Menunggu' },
{ id: 'disetujui', label: 'Disetujui' }
].map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`px-5 py-2 rounded-full font-semibold text-sm whitespace-nowrap transition-colors ${
activeTab === tab.id
? 'bg-slate-800 text-white'
: 'bg-gray-100 text-gray-700'
}`}
>
{tab.label}
</button>
))}
</div>
</div>
<div className="flex-1 overflow-y-auto pb-20">
<div className="p-4">
{/* SPK List */}
<div className="space-y-3">
{filteredSPK.map((spk) => (
<div
key={spk.id}
className="bg-white rounded-2xl p-4 shadow-sm"
>
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<p className="text-xs text-gray-500 mb-1">NOMOR DOKUMEN</p>
<h3 className="font-bold text-gray-900 mb-2">{spk.nomorDokumen}</h3>
</div>
<span className={`${spk.statusColor} text-xs px-3 py-1 rounded-full font-semibold`}>
{spk.status}
</span>
</div>
<div className="space-y-2 mb-3">
<div className="flex items-center gap-2">
<svg className="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
<div>
<p className="text-xs text-gray-500">PELANGGAN</p>
<p className="text-sm font-semibold text-gray-900">{spk.pelanggan}</p>
</div>
</div>
<div className="flex items-center gap-2">
<svg className="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 24 24">
<path d="M18.92 6.01C18.72 5.42 18.16 5 17.5 5h-11c-.66 0-1.21.42-1.42 1.01L3 12v8c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1h12v1c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-8l-2.08-5.99zM6.5 16c-.83 0-1.5-.67-1.5-1.5S5.67 13 6.5 13s1.5.67 1.5 1.5S7.33 16 6.5 16zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zM5 11l1.5-4.5h11L19 11H5z"/>
</svg>
<div>
<p className="text-xs text-gray-500">UNIT KENDARAAN</p>
<p className="text-sm font-semibold text-gray-900">{spk.unitKendaraan}</p>
</div>
</div>
</div>
<div className="flex items-center justify-between pt-3 border-t border-gray-100">
<div className="flex items-center gap-1 text-xs text-gray-500">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<span>{spk.tanggal}</span>
</div>
<button
onClick={() => navigate('/detail-spk')}
className="text-blue-600 text-sm font-semibold flex items-center gap-1"
>
Detail
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
))}
</div>
{filteredSPK.length === 0 && (
<div className="text-center py-12">
<p className="text-gray-500">Tidak ada SPK ditemukan</p>
</div>
)}
{/* Ask Dora Button */}
<button
onClick={() => navigate('/ask-dora')}
className="fixed bottom-24 right-4 bg-slate-800 text-white px-4 py-3 rounded-full shadow-lg flex items-center gap-2 z-10"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" />
</svg>
<span className="text-sm font-semibold">ASK DORA</span>
</button>
</div>
</div>
<BottomNav />
</div>
);
};
export default Activities;
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import BottomNav from '../components/BottomNav';
const AskDora = () => {
const navigate = useNavigate();
const [message, setMessage] = useState('');
const quickActions = [
{ icon: '📊', text: 'Cek target bulan ini' },
{ icon: '📋', text: 'Daftar follow-up hari ini' },
{ icon: '📝', text: 'Bantuan input SPK' }
];
return (
<div className="flex flex-col h-screen bg-gray-50">
{/* Header */}
<div className="sticky top-0 z-50 bg-white px-4 py-3 flex items-center justify-between shadow-sm">
<div className="flex items-center gap-3">
<button onClick={() => navigate('/')} className="text-gray-700">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</button>
<div className="w-10 h-10 bg-slate-800 rounded-full flex items-center justify-center">
<span className="text-white text-xl">🤖</span>
</div>
<div>
<h1 className="font-bold text-gray-900">DORA</h1>
<p className="text-xs text-green-600">ONLINE</p>
</div>
</div>
<div className="flex items-center gap-3">
<button className="text-gray-600">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</button>
<button className="text-gray-600">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" />
</svg>
</button>
</div>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto pb-24">
<div className="p-4 space-y-4">
{/* Welcome Message */}
<div className="text-center py-8">
<h2 className="text-2xl font-bold text-gray-900 mb-2">Selamat Pagi, Rian</h2>
<p className="text-gray-600">
Kelola unit, prospek, dan target penjualan Anda dengan asisten AI yang lebih cerdas.
</p>
</div>
{/* DORA Message */}
<div className="flex gap-3">
<div className="w-10 h-10 bg-slate-800 rounded-full flex items-center justify-center flex-shrink-0">
<span className="text-white text-xl">🤖</span>
</div>
<div className="flex-1">
<div className="bg-white rounded-2xl rounded-tl-none p-4 shadow-sm">
<p className="text-gray-800">
Halo, Rian! Saya DORA, asisten digital Anda. Ada yang bisa saya bantu untuk mempermudah pekerjaan Anda hari ini? 😊
</p>
</div>
<p className="text-xs text-gray-500 mt-1 ml-2">09:41 AM</p>
</div>
</div>
{/* Quick Actions */}
<div className="space-y-2">
{quickActions.map((action, index) => (
<button
key={index}
className="w-full bg-white rounded-xl p-4 shadow-sm text-left flex items-center gap-3 hover:bg-gray-50 transition-colors"
>
<span className="text-2xl">{action.icon}</span>
<span className="text-gray-800 font-medium">{action.text}</span>
<svg className="w-5 h-5 text-gray-400 ml-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</button>
))}
</div>
</div>
</div>
{/* Input Area - Above Bottom Nav */}
<div className="sticky bottom-16 z-40 bg-gray-50 border-t border-gray-200 px-4 py-3">
<div className="flex items-center gap-3">
<button className="text-gray-400 hover:text-gray-600">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</button>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Tulis pesan..."
className="flex-1 bg-white rounded-lg px-4 py-3 text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-slate-800 border border-gray-200"
/>
<button className="bg-slate-800 text-white p-3 rounded-full hover:bg-slate-700 transition-colors flex-shrink-0">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</button>
</div>
</div>
<BottomNav />
</div>
);
};
export default AskDora;
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment