Compare commits
6 Commits
v1.0.1-alpha.1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 757cd1ca9e | |||
| 820f8e46d9 | |||
| db3ac37042 | |||
| 605ccf5a7b | |||
| 06b687f408 | |||
| d0f5e368e3 |
+43
-18
@@ -1,9 +1,12 @@
|
||||
APP_NAME="SUTERA 3.0 Dev"
|
||||
APP_NAME="MYKOPKB 1.0"
|
||||
APP_ENV=local
|
||||
APP_KEY=base64:0dM5HYEFRu9P6ni51QTUZm5mQ23znc/s/aebLTRUGxU=
|
||||
APP_DEBUG=true
|
||||
APP_TIMEZONE=Asia/Kuala_Lumpur
|
||||
APP_URL=http://localhost
|
||||
FRONTEND_URL=http://localhost:5173
|
||||
|
||||
TELESCOPE_ENABLED=true
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
@@ -24,8 +27,8 @@ LOG_LEVEL=debug
|
||||
DB_CONNECTION=pgsql
|
||||
DB_HOST=pgsql
|
||||
DB_PORT=5432
|
||||
DB_DATABASE=sutera
|
||||
DB_USERNAME=suterauser
|
||||
DB_DATABASE=mykopkb
|
||||
DB_USERNAME=mykopkbuser
|
||||
DB_PASSWORD=password
|
||||
|
||||
SESSION_DRIVER=redis
|
||||
@@ -38,8 +41,11 @@ BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=database
|
||||
|
||||
HORIZON_WORKER_MEMORY=512
|
||||
HORIZON_WORKER_TIMEOUT=3600
|
||||
|
||||
CACHE_STORE=redis
|
||||
CACHE_PREFIX=sutera_cache
|
||||
CACHE_PREFIX=mykopkb_cache
|
||||
|
||||
MEMCACHED_HOST=memcached
|
||||
|
||||
@@ -54,8 +60,9 @@ MAIL_HOST=mailpit
|
||||
MAIL_PORT=1025
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_ADDRESS="no-reply@army.mil.my"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
MAIL_LOGO_PATH="images/logo/logo-kopkb-letterhead.svg"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
@@ -73,18 +80,6 @@ VITE_APP_NAME="${APP_NAME}"
|
||||
EXTERNAL_API_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsibWJzcC1yZXNvdXJjZUlkIl0sInRpbWVvdXRQZXJpb2QiOjkwMCwidXNlcl9uYW1lIjoic3BhLXVzZXIiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXhwIjoxNzI4NTgzNjEyLCJqdGkiOiJjMmU2OTkxOS0yZDU3LTQ5YmYtYTVmZS1lZjVmMzIyNDZkZDIiLCJjbGllbnRfaWQiOiJzcGEtY2xpZW50In0.LE2byaRzsYkchzI4ox9Gz2u6jNmTqZQa-_ttR2PY8e8"
|
||||
EXTERNAL_API_BASE_URL=http://20.11.32.134/rest/td/gasset/asset
|
||||
|
||||
SSO_SECRET="Y6g#Rb@Km=Nz]o^X+fEC~<W/LP%qh;M}9T&v!["
|
||||
SSO_VALIDATION_URL=http://host.docker.internal:8080/api/sso-validate
|
||||
SSO_MAX_ATTEMPTS=5
|
||||
SSO_TOKEN_EXPIRATION=300
|
||||
SSO_AUTO_ACTIVATE_USERS=true
|
||||
SSO_LOG_ACTIVITIES=true
|
||||
SSO_CONNECTION_TIMEOUT=10
|
||||
SSO_RESPONSE_TIMEOUT=30
|
||||
SSO_PROXY_URL=http://20.9.91.1:3128
|
||||
SSO_ENABLE_FALLBACK=true
|
||||
SSO_PROXY_ENABLED=false
|
||||
|
||||
SANCTUM_STATEFUL_DOMAINS=http://localhost
|
||||
|
||||
BLOCK_API_TOOLS_IN_PRODUCTION=true
|
||||
@@ -97,5 +92,35 @@ ENABLE_API_KEY_AUTH=true
|
||||
API_VALID_KEYS=86f58825e5c1e0872d8786092d47e3bd,6461fc5390660c55dc47bdfcd85ed9ae
|
||||
API_LOG_KEY_USAGE=true
|
||||
|
||||
# HttpOnly Sanctum token cookie (set on api.* domain)
|
||||
AUTH_COOKIE_NAME=auth_token
|
||||
AUTH_COOKIE_LIFETIME=360
|
||||
AUTH_COOKIE_SECURE=true
|
||||
AUTH_COOKIE_SAME_SITE=lax
|
||||
AUTH_COOKIE_EXPOSE_TOKEN=false
|
||||
|
||||
# Browsershot (PDF generation via headless Chrome + Puppeteer in be/)
|
||||
# Required in Docker/Sail. After first sail up: sail npm install && sail npx puppeteer browsers install chrome-headless-shell
|
||||
BROWSERSHOT_NO_SANDBOX=true
|
||||
# BROWSERSHOT_NODE_BINARY=
|
||||
# BROWSERSHOT_NPM_BINARY=
|
||||
# BROWSERSHOT_NODE_MODULE_PATH=
|
||||
# BROWSERSHOT_CHROME_PATH=
|
||||
# BROWSERSHOT_TIMEOUT=60
|
||||
|
||||
PUBLIC_PROFILE_TOKEN_TTL_DAYS=7
|
||||
FRONTEND_URL=https://mykopkb.koppkb.com
|
||||
|
||||
EMAIL_VERIFICATION_EXPIRE_MINUTES=60
|
||||
EMAIL_VERIFICATION_REDIRECT_PATH=/profile-overview-2
|
||||
|
||||
PHONE_VERIFICATION_OTP_EXPIRY_MINUTES=10
|
||||
PHONE_VERIFICATION_TOKEN_EXPIRY_MINUTES=30
|
||||
PHONE_VERIFICATION_OTP_MAX_ATTEMPTS=5
|
||||
|
||||
SMS_DRIVER=log
|
||||
ONEWAYSMS_BASE_URL=http://gateway.onewaysms.com.my:10001/api.aspx
|
||||
ONEWAYSMS_API_USERNAME=API7LO0ELJTAU
|
||||
ONEWAYSMS_API_PASSWORD=API7LO0ELJTAU7LO0E
|
||||
ONEWAYSMS_SENDER_ID=INFO
|
||||
|
||||
EXTERNAL_SYSTEM_SSO_ISSUER=mykopkb
|
||||
@@ -1,88 +0,0 @@
|
||||
APP_NAME="SUTERA 3.0"
|
||||
APP_ENV=staging
|
||||
APP_KEY=base64:9vnA0vvAweC8C+/WKyEDOziWp4QPsdKZ/blgDZXIbKQ=
|
||||
APP_DEBUG=false
|
||||
APP_TIMEZONE=Asia/Kuala_Lumpur
|
||||
APP_URL=https://sutera.koppkb.com
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
APP_MAINTENANCE_STORE=database
|
||||
|
||||
PHP_CLI_SERVER_WORKERS=8
|
||||
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=error
|
||||
|
||||
DB_CONNECTION=pgsql
|
||||
DB_HOST=pgsql
|
||||
DB_PORT=5432
|
||||
DB_DATABASE=sutera
|
||||
DB_USERNAME=suterauser
|
||||
DB_PASSWORD=Aretus@2025
|
||||
DB_ROOT_PASSWORD=Sutera_Staging@root_2025
|
||||
|
||||
SESSION_DRIVER=redis
|
||||
SESSION_LIFETIME=60
|
||||
SESSION_ENCRYPT=true
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=redis
|
||||
|
||||
CACHE_STORE=redis
|
||||
CACHE_PREFIX=sutera_cache
|
||||
CACHE_DRIVER=redis
|
||||
|
||||
MEMCACHED_HOST=memcached
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=sutera_redis@2025
|
||||
REDIS_DB=0
|
||||
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_SCHEME=null
|
||||
MAIL_HOST=mailpit
|
||||
MAIL_PORT=1025
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
# VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
# VITE_PUSHER_HOST="${PUSHER_HOST}"
|
||||
# VITE_PUSHER_PORT="${PUSHER_PORT}"
|
||||
# VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
|
||||
# VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
|
||||
EXTERNAL_API_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsibWJzcC1yZXNvdXJjZUlkIl0sInRpbWVvdXRQZXJpb2QiOjkwMCwidXNlcl9uYW1lIjoic3BhLXVzZXIiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXhwIjoxNzI4NTgzNjEyLCJqdGkiOiJjMmU2OTkxOS0yZDU3LTQ5YmYtYTVmZS1lZjVmMzIyNDZkZDIiLCJjbGllbnRfaWQiOiJzcGEtY2xpZW50In0.LE2byaRzsYkchzI4ox9Gz2u6jNmTqZQa-_ttR2PY8e8"
|
||||
EXTERNAL_API_BASE_URL=http://20.11.32.134/rest/td/gasset/asset
|
||||
|
||||
SSO_SECRET="Y6g#Rb@Km=Nz]o^X+fEC~<W/LP%qh;M}9T&v!["
|
||||
SSO_VALIDATION_URL=http://host.docker.internal:8080/api/sso-validate
|
||||
SSO_MAX_ATTEMPTS=5
|
||||
SSO_TOKEN_EXPIRATION=300
|
||||
SSO_AUTO_ACTIVATE_USERS=true
|
||||
SSO_LOG_ACTIVITIES=true
|
||||
SSO_CONNECTION_TIMEOUT=10
|
||||
SSO_RESPONSE_TIMEOUT=30
|
||||
SSO_PROXY_URL=http://20.9.91.1:3128
|
||||
SSO_ENABLE_FALLBACK=true
|
||||
@@ -38,6 +38,14 @@ class VerifyEmailNotification extends Notification
|
||||
protected function verificationUrl(User $notifiable): string
|
||||
{
|
||||
$expireMinutes = (int) config('auth.verification.expire_minutes', 60);
|
||||
$root = rtrim((string) config('app.url'), '/');
|
||||
|
||||
// Prefer APP_URL over the current request host so queued/local proxy
|
||||
// contexts do not embed http://localhost in production emails.
|
||||
URL::forceRootUrl($root);
|
||||
if ($scheme = parse_url($root, PHP_URL_SCHEME)) {
|
||||
URL::forceScheme($scheme);
|
||||
}
|
||||
|
||||
return URL::temporarySignedRoute(
|
||||
'verification.verify',
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace Modules\Auth\Providers;
|
||||
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
@@ -24,4 +26,13 @@ class EventServiceProvider extends ServiceProvider
|
||||
* Configure the proper event listeners for email verification.
|
||||
*/
|
||||
protected function configureEmailVerification(): void {}
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
// Framework auto-registers SendEmailVerificationNotification on Registered.
|
||||
// Verification is user-initiated via the email verification banner instead.
|
||||
$this->app->booted(function () {
|
||||
Event::forget(Registered::class);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
|
||||
@section('letter-reference', $application->application_number)
|
||||
|
||||
@section('letter-date', now()->translatedFormat('d F Y'))
|
||||
@section('letter-date', now()->locale('ms')->translatedFormat('d F Y'))
|
||||
|
||||
@section('letter-recipient')
|
||||
<strong>{{ $applicant->name }}</strong><br>
|
||||
{!! nl2br(e($applicant->address)) !!}<br>
|
||||
{{ $applicant->postcode }}
|
||||
{!! nl2br(e($applicant->address)) !!}
|
||||
@endsection
|
||||
|
||||
@section('letter-honorific')
|
||||
|
||||
@@ -444,7 +444,7 @@ class MembershipApplicationService
|
||||
'application' => $application,
|
||||
'applicant' => $applicant,
|
||||
'boardMeetingReference' => $boardMeetingReference,
|
||||
'boardMeetingDate' => Carbon::parse($boardMeetingDate)->translatedFormat('d F Y'),
|
||||
'boardMeetingDate' => Carbon::parse($boardMeetingDate)->locale('ms')->translatedFormat('d F Y'),
|
||||
'isPassed' => $isPassed,
|
||||
'letterSubject' => $isPassed
|
||||
? 'KELULUSAN PERMOHONAN MENJADI ANGGOTA KOPERASI PERMODALAN KELANTAN BERHAD (KoPKB)'
|
||||
|
||||
@@ -122,7 +122,7 @@ class UserController extends BaseCrudController
|
||||
'ic_number' => 'sometimes|string|max:255|unique:users,ic_number,'.$user->id,
|
||||
'position' => 'sometimes|string|max:255',
|
||||
'phone_number' => 'sometimes|string|max:255',
|
||||
'image' => 'sometimes|image|mimes:jpeg,png,jpg,gif|max:2048',
|
||||
'image' => 'sometimes|image|mimes:jpeg,png,jpg,gif|max:5120',
|
||||
'image_url' => 'sometimes|string|max:255',
|
||||
'gender' => 'sometimes|string|max:255',
|
||||
'marriage_status' => 'sometimes|string|max:255',
|
||||
|
||||
@@ -94,19 +94,31 @@ class FortifyServiceProvider extends ServiceProvider
|
||||
|
||||
Fortify::authenticateUsing(function (Request $request) {
|
||||
$user = User::where('email', $request->email)->first();
|
||||
|
||||
// Bypass password check in local or development environments
|
||||
$shouldBypassPassword = config('app.env', 'local');
|
||||
|
||||
if ($user && ($shouldBypassPassword || Hash::check($request->password, $user->password))) {
|
||||
if (! $user->canAuthenticate()) {
|
||||
throw ValidationException::withMessages([
|
||||
'email' => [$user->getLoginRestrictionMessage()],
|
||||
]);
|
||||
}
|
||||
$invalidCredentialsMessage = 'Emel atau kata laluan tidak sah.';
|
||||
|
||||
return $user;
|
||||
$failLogin = function () use ($invalidCredentialsMessage): never {
|
||||
throw ValidationException::withMessages([
|
||||
'email' => [$invalidCredentialsMessage],
|
||||
]);
|
||||
};
|
||||
|
||||
if (! $user) {
|
||||
$failLogin();
|
||||
}
|
||||
|
||||
$shouldBypassPassword = app()->environment('local');
|
||||
|
||||
if (! $shouldBypassPassword && ! Hash::check($request->password, $user->password)) {
|
||||
$failLogin();
|
||||
}
|
||||
|
||||
if (! $user->canAuthenticate()) {
|
||||
throw ValidationException::withMessages([
|
||||
'email' => [$user->getLoginRestrictionMessage()],
|
||||
]);
|
||||
}
|
||||
|
||||
return $user;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,10 +59,13 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
// Handle Validation exceptions (422 errors)
|
||||
$exceptions->render(function (Illuminate\Validation\ValidationException $e, $request) {
|
||||
if ($request->expectsJson()) {
|
||||
$errors = $e->errors();
|
||||
$firstMessage = collect($errors)->flatten()->first() ?? 'Data tidak sah.';
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Data tidak sah.',
|
||||
'errors' => $e->errors(),
|
||||
'message' => $firstMessage,
|
||||
'errors' => $errors,
|
||||
], 422);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -163,6 +163,9 @@ COPY --from=builder /usr/local/bin/docker-php-ext-* /usr/local/bin/
|
||||
# Use the recommended production PHP configuration
|
||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
||||
|
||||
# Custom PHP overrides (upload limits, etc.) — official image loads conf.d/*.ini
|
||||
COPY be/docker/common/unified/php/uploads.ini /usr/local/etc/php/conf.d/uploads.ini
|
||||
|
||||
# Enable PHP-FPM status page via separate config file (avoid modifying zz-docker.conf)
|
||||
RUN echo '[www]' > /usr/local/etc/php-fpm.d/zzz-status.conf && \
|
||||
echo 'pm.status_path = /status' >> /usr/local/etc/php-fpm.d/zzz-status.conf
|
||||
|
||||
@@ -9,6 +9,8 @@ http {
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
client_max_body_size 128M;
|
||||
|
||||
# Logging
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
; Keep above Laravel validation (max:10240 = 10MB)
|
||||
upload_max_filesize = 12M
|
||||
post_max_size = 12M
|
||||
@@ -8,6 +8,8 @@ http {
|
||||
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
client_max_body_size 128M;
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
@@ -9,6 +9,8 @@ http {
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
client_max_body_size 128M;
|
||||
|
||||
# Logging
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
@@ -23,7 +23,7 @@ export const EMPLOYERS = [
|
||||
},
|
||||
{
|
||||
name: 'Xtra Biz Sdn Bhd (XBSB)',
|
||||
address: "Lot 241, Jalan Dato' Cik Arif, Taman Mutiara 1, 15000 Kota Bharu, Kelantan.",
|
||||
address: "-",
|
||||
},
|
||||
{
|
||||
name: 'Pesara',
|
||||
|
||||
@@ -692,7 +692,12 @@ function stepLabelClass(stepId: number) {
|
||||
<template v-else-if="currentStep === 2">
|
||||
<Field class="col-span-12">
|
||||
<FieldLabel for="address">Alamat</FieldLabel>
|
||||
<Textarea id="address" v-model="form.applicant.address" rows="3" />
|
||||
<Textarea
|
||||
id="address"
|
||||
v-model="form.applicant.address"
|
||||
rows="3"
|
||||
placeholder="Lot Pt 448, Tingkat 1, Jalan Kuala Krai, Batu 3, Wakaf Che Yeh, 15150 Kota Bharu, Kelantan."
|
||||
/>
|
||||
<FieldError v-if="fieldErrors['applicant.address']">{{ fieldErrors['applicant.address'] }}</FieldError>
|
||||
</Field>
|
||||
<Field class="col-span-12 sm:col-span-4">
|
||||
|
||||
@@ -65,7 +65,7 @@ const DOCUMENT_TYPE_LABELS: Record<string, string> = {
|
||||
[RESULT_LETTER_DOCUMENT_TYPE]: 'Surat Keputusan',
|
||||
}
|
||||
|
||||
const BOARD_MEETING_REFERENCE_PREFIX = 'Mesyuarat Lembaga Bil.'
|
||||
const BOARD_MEETING_REFERENCE_PREFIX = 'Mesyuarat Lembaga KoPKB Bil. '
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
@@ -58,6 +58,7 @@ import { ADMIN_ATTACHMENT_DOCUMENT_TYPE } from '../types/membership-application.
|
||||
import {
|
||||
APPLICANT_DOCUMENT_UPLOAD_TYPES,
|
||||
DOCUMENT_TYPE_LABELS,
|
||||
EMPLOYER_OPTIONS,
|
||||
GENDER_OPTIONS,
|
||||
MARRIAGE_STATUS_OPTIONS,
|
||||
RELATIONSHIP_OPTIONS,
|
||||
@@ -67,6 +68,7 @@ import {
|
||||
createEmptyFormState,
|
||||
createSelectCollection,
|
||||
detailToFormState,
|
||||
getEmployerAddress,
|
||||
labelToApiValue,
|
||||
} from '../utils/membership-application-form.utils'
|
||||
|
||||
@@ -112,6 +114,7 @@ const referenceLookupLoading = reactive({
|
||||
const genderCollection = createSelectCollection(GENDER_OPTIONS)
|
||||
const marriageStatusCollection = createSelectCollection(MARRIAGE_STATUS_OPTIONS)
|
||||
const relationshipCollection = createSelectCollection(RELATIONSHIP_OPTIONS)
|
||||
const employerCollection = createSelectCollection(EMPLOYER_OPTIONS)
|
||||
|
||||
const canEdit = computed(
|
||||
() =>
|
||||
@@ -123,6 +126,7 @@ const genderInitial = computed(() => apiValueToLabel(GENDER_OPTIONS, form.applic
|
||||
const marriageStatusInitial = computed(() =>
|
||||
apiValueToLabel(MARRIAGE_STATUS_OPTIONS, form.applicant.marriage_status),
|
||||
)
|
||||
const employerInitial = computed(() => apiValueToLabel(EMPLOYER_OPTIONS, form.applicant.employer_name))
|
||||
|
||||
const workflowProgress = computed(() =>
|
||||
application.value ? getWorkflowProgress(application.value.status) : null,
|
||||
@@ -309,6 +313,14 @@ function setMarriageStatusValue(details: { value: string[] }) {
|
||||
delete fieldErrors['applicant.marriage_status']
|
||||
}
|
||||
|
||||
function setEmployerValue(details: { value: string[] }) {
|
||||
const employerName = labelToApiValue(EMPLOYER_OPTIONS, details.value[0])
|
||||
form.applicant.employer_name = employerName
|
||||
form.applicant.employer_address = getEmployerAddress(employerName)
|
||||
delete fieldErrors['applicant.employer_name']
|
||||
delete fieldErrors['applicant.employer_address']
|
||||
}
|
||||
|
||||
function setHeirRelationshipValue(index: number, details: { value: string[] }) {
|
||||
const heir = form.heirs[index]
|
||||
if (!heir) return
|
||||
@@ -859,8 +871,24 @@ onUnmounted(() => {
|
||||
<TabsContent value="employment" class="mt-6">
|
||||
<div class="grid grid-cols-12 gap-4 gap-y-5">
|
||||
<Field class="col-span-12 sm:col-span-6">
|
||||
<FieldLabel for="employer_name">Nama Majikan</FieldLabel>
|
||||
<Input id="employer_name" v-model="form.applicant.employer_name" type="text" :disabled="!canEdit" />
|
||||
<FieldLabel>Nama Majikan</FieldLabel>
|
||||
<SelectRoot :key="`employer-${form.applicant.employer_name}`" class="w-full"
|
||||
:collection="employerCollection" :default-value="employerInitial" :disabled="!canEdit"
|
||||
@value-change="setEmployerValue">
|
||||
<SelectControl>
|
||||
<SelectTrigger :aria-invalid="!!fieldErrors['applicant.employer_name']">
|
||||
<SelectValueText placeholder="Pilih majikan" />
|
||||
</SelectTrigger>
|
||||
</SelectControl>
|
||||
<SelectContent>
|
||||
<SelectItemGroup>
|
||||
<SelectItemGroupLabel>Nama Majikan</SelectItemGroupLabel>
|
||||
<SelectItem v-for="item in employerCollection.items" :key="item.label" :item="item">
|
||||
<SelectItemText>{{ item.label }}</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectItemGroup>
|
||||
</SelectContent>
|
||||
</SelectRoot>
|
||||
<FieldError v-if="fieldErrors['applicant.employer_name']">{{ fieldErrors['applicant.employer_name'] }}
|
||||
</FieldError>
|
||||
</Field>
|
||||
@@ -872,7 +900,7 @@ onUnmounted(() => {
|
||||
</Field>
|
||||
<Field class="col-span-12">
|
||||
<FieldLabel for="employer_address">Alamat Majikan</FieldLabel>
|
||||
<Textarea id="employer_address" v-model="form.applicant.employer_address" rows="3" :disabled="!canEdit" />
|
||||
<Textarea id="employer_address" v-model="form.applicant.employer_address" rows="3" disabled />
|
||||
<FieldError v-if="fieldErrors['applicant.employer_address']">{{ fieldErrors['applicant.employer_address']
|
||||
}}</FieldError>
|
||||
</Field>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import dayjs from 'dayjs'
|
||||
import * as select from '@zag-js/select'
|
||||
import { EMPLOYERS } from '@/constants/employers'
|
||||
import type {
|
||||
MembershipApplicationDetail,
|
||||
MembershipApplicationDocumentsForm,
|
||||
@@ -35,6 +36,15 @@ export const RELATIONSHIP_OPTIONS: SelectOption[] = [
|
||||
{ label: 'Lain-lain', value: 'Lain-lain' },
|
||||
]
|
||||
|
||||
export const EMPLOYER_OPTIONS: SelectOption[] = EMPLOYERS.map((employer) => ({
|
||||
label: employer.name,
|
||||
value: employer.name,
|
||||
}))
|
||||
|
||||
export function getEmployerAddress(name: string): string {
|
||||
return EMPLOYERS.find((employer) => employer.name === name)?.address ?? ''
|
||||
}
|
||||
|
||||
export const DOCUMENT_TYPE_LABELS: Record<string, string> = {
|
||||
ic_copy: 'Salinan Kad Pengenalan',
|
||||
photo: 'Gambar Passport',
|
||||
|
||||
@@ -190,6 +190,9 @@ onMounted(async () => {
|
||||
No. {{ authStore.userMemberNumber }}
|
||||
</Badge>
|
||||
</div>
|
||||
<p class="mt-2 text-xs opacity-70">
|
||||
Gambar profil: maksimum 2 MB (JPEG, PNG, GIF).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -318,7 +318,6 @@ async function confirmRestore() {
|
||||
const headers: TableHeader[] = [
|
||||
{ title: 'Bil.', key: '#', sortable: false },
|
||||
{ title: 'Name', key: 'name', sortable: true },
|
||||
{ title: 'Jenis Anggota', key: 'member_type', sortable: true },
|
||||
{
|
||||
title: 'Unit',
|
||||
key: 'company_name',
|
||||
|
||||
Reference in New Issue
Block a user