Bugfix/testing (#4)
Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local> Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local> Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
[x] create qr and teller account
|
||||
[x] increase size of gold display
|
||||
+1
-30
@@ -4,7 +4,7 @@ services:
|
||||
container_name: qms-mysql
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${MYSQL_HOST_PORT:-3307}:3306"
|
||||
- "3306:3306"
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-password}
|
||||
MYSQL_DATABASE: ${MYSQL_DATABASE:-qms}
|
||||
@@ -27,37 +27,8 @@ services:
|
||||
networks:
|
||||
- qms-net
|
||||
|
||||
backend:
|
||||
image: ${BACKEND_IMAGE:-qms-backend}:${IMAGE_TAG:-local}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: qms-backend
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
# Host port can be changed via BACKEND_HOST_PORT in .env (default 8080)
|
||||
- "${BACKEND_HOST_PORT:-8080}:8080"
|
||||
environment:
|
||||
SERVER_PORT: 8080
|
||||
# Use Docker service name "mysql", not localhost
|
||||
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/${MYSQL_DATABASE:-qms}?allowPublicKeyRetrieval=true&useSSL=false
|
||||
SPRING_DATASOURCE_USERNAME: root
|
||||
SPRING_DATASOURCE_PASSWORD: ${MYSQL_ROOT_PASSWORD:-password}
|
||||
JWT_SECRET_KEY: ${JWT_SECRET_KEY:-a68uiaDQ0V3iLjF4DqMuS13GAVwkut55dlFbGCLyXTF}
|
||||
ADS_UPLOAD_DIR: /app/uploads/ads
|
||||
NOTIFICATIONS_MOCK: ${NOTIFICATIONS_MOCK:-true}
|
||||
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-dummy-google-client-id}
|
||||
volumes:
|
||||
- qms-uploads:/app/uploads
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- qms-net
|
||||
|
||||
volumes:
|
||||
qms-mysql-data:
|
||||
qms-uploads:
|
||||
|
||||
networks:
|
||||
qms-net:
|
||||
|
||||
+2
-1
@@ -33,6 +33,7 @@ public class DefaultAdminService implements AdminService {
|
||||
private final RoleService roleService;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final AuthService authService;
|
||||
|
||||
public DefaultAdminService(final UserRepository userRepository,
|
||||
final UserService userService,
|
||||
final TwoFactorService twoFactorService,
|
||||
@@ -50,7 +51,7 @@ public class DefaultAdminService implements AdminService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> findUsersByCode(final String tenantCode, final String roleName){
|
||||
public List<User> findUsersByCode(final String tenantCode, final String roleName) {
|
||||
final Set<RoleName> roleNameSet = Set.of(RoleName.valueOf(roleName));
|
||||
return this.userRepository.findAllByTenant_CodeAndRoles_NameIn(tenantCode, roleNameSet);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@@ -30,7 +31,8 @@ public class AdminController {
|
||||
|
||||
@PostMapping("/{code}")
|
||||
@PreAuthorize("hasAnyRole('ROLE_SUPER_ADMIN', 'ROLE_BRANCH_ADMIN', 'ROLE_STAFF_ADMIN')")
|
||||
public ResponseEntity getUsers(@RequestBody RoleRequest request, @PathVariable(name = "code") final String tenantCode) {
|
||||
public ResponseEntity<List<UserDto>> getUsers(@RequestBody RoleRequest request,
|
||||
@PathVariable(name = "code") final String tenantCode) {
|
||||
RoleName roleName;
|
||||
try {
|
||||
roleName = RoleName.valueOf(request.roleName);
|
||||
@@ -39,7 +41,7 @@ public class AdminController {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
||||
if(this.authService.canOnlyCRUDUser(roleName)){
|
||||
if (this.authService.canOnlyCRUDUser(roleName)) {
|
||||
logger.warn("Only super admin can read admins");
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
@@ -52,8 +54,7 @@ public class AdminController {
|
||||
return ResponseEntity.ok().body(
|
||||
this.adminService.findUsersByCode(tenantCode, request.roleName).stream()
|
||||
.map(UserDto::fromEntity)
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
.collect(Collectors.toList()));
|
||||
} catch (EntityNotFoundException e) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
@@ -61,7 +62,8 @@ public class AdminController {
|
||||
|
||||
@PostMapping("/{code}/user")
|
||||
@PreAuthorize("hasAnyRole('ROLE_SUPER_ADMIN', 'ROLE_BRANCH_ADMIN', 'ROLE_STAFF_ADMIN')")
|
||||
public ResponseEntity addUser(@RequestBody final AdminRequest request, @PathVariable(name = "code") final String tenantCode) throws AuthException {
|
||||
public ResponseEntity<UserDto> addUser(@RequestBody final AdminRequest request,
|
||||
@PathVariable(name = "code") final String tenantCode) throws AuthException {
|
||||
RoleName roleName;
|
||||
try {
|
||||
roleName = RoleName.valueOf(request.roleName);
|
||||
@@ -70,7 +72,7 @@ public class AdminController {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
||||
if(this.authService.canOnlyCRUDUser(roleName)){
|
||||
if (this.authService.canOnlyCRUDUser(roleName)) {
|
||||
logger.warn("Only super admin can add admin");
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
@@ -95,7 +97,8 @@ public class AdminController {
|
||||
|
||||
@PutMapping("/{code}/user/{userId}")
|
||||
@PreAuthorize("hasAnyRole('ROLE_SUPER_ADMIN', 'ROLE_BRANCH_ADMIN', 'ROLE_STAFF_ADMIN')")
|
||||
public ResponseEntity updateAdmin(@RequestBody final UserDto request, @PathVariable(name = "code") final String tenantCode, @PathVariable(name = "userId") final long adminId) {
|
||||
public ResponseEntity<SimpleMessageDto> updateAdmin(@RequestBody final UserDto request,
|
||||
@PathVariable(name = "code") final String tenantCode, @PathVariable(name = "userId") final long adminId) {
|
||||
if (!this.authService.canChangeTenant(tenantCode)) {
|
||||
logger.warn("Admin does not belong to the specified tenant");
|
||||
return ResponseEntity.badRequest().build();
|
||||
@@ -112,7 +115,8 @@ public class AdminController {
|
||||
|
||||
@DeleteMapping("/{code}/user/{userId}")
|
||||
@PreAuthorize("hasAnyRole('ROLE_SUPER_ADMIN', 'ROLE_BRANCH_ADMIN', 'ROLE_STAFF_ADMIN')")
|
||||
public ResponseEntity removeAdmin(@PathVariable(name = "code") final String tenantCode, @PathVariable(name = "userId") final long adminId) {
|
||||
public ResponseEntity<SimpleMessageDto> removeAdmin(@PathVariable(name = "code") final String tenantCode,
|
||||
@PathVariable(name = "userId") final long adminId) {
|
||||
if (!this.authService.canChangeTenant(tenantCode)) {
|
||||
logger.warn("Admin does not belong to the specified tenant");
|
||||
return ResponseEntity.badRequest().build();
|
||||
@@ -130,6 +134,6 @@ public class AdminController {
|
||||
public record AdminRequest(String email, String password, String roleName) {
|
||||
}
|
||||
|
||||
public record RoleRequest(String roleName){
|
||||
public record RoleRequest(String roleName) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class AdvertisementController {
|
||||
}
|
||||
|
||||
@GetMapping("/media/{adId}")
|
||||
public ResponseEntity streamMedia(@PathVariable final long adId) {
|
||||
public ResponseEntity<Resource> streamMedia(@PathVariable final long adId) {
|
||||
try {
|
||||
final Advertisement advertisement = this.advertisementService.findById(adId);
|
||||
final Resource resource = this.advertisementService.loadMedia(adId);
|
||||
@@ -80,8 +80,7 @@ public class AdvertisementController {
|
||||
title,
|
||||
durationSeconds,
|
||||
sortOrder,
|
||||
active
|
||||
);
|
||||
active);
|
||||
return ResponseEntity.ok().body(AdvertisementDto.fromEntity(created));
|
||||
} catch (final Exception exception) {
|
||||
return ResponseEntity.badRequest().body(new SimpleMessageDto(exception.getMessage()));
|
||||
@@ -121,8 +120,7 @@ public class AdvertisementController {
|
||||
request.title(),
|
||||
request.durationSeconds(),
|
||||
request.sortOrder(),
|
||||
request.active()
|
||||
);
|
||||
request.active());
|
||||
return ResponseEntity.ok().body(AdvertisementDto.fromEntity(updated));
|
||||
} catch (final Exception exception) {
|
||||
return ResponseEntity.badRequest().body(new SimpleMessageDto(exception.getMessage()));
|
||||
|
||||
@@ -22,6 +22,9 @@ type Props = {
|
||||
initialBranchId?: number;
|
||||
};
|
||||
|
||||
/** Prevents spam of "Dapatkan tiket lain" right after getting a number. */
|
||||
const ANOTHER_TICKET_COOLDOWN_MS = 10_000;
|
||||
|
||||
function pickLatestTicket(
|
||||
tickets: Ticket[],
|
||||
branchId?: number
|
||||
@@ -61,8 +64,27 @@ export default function CustomerTicketFlow({
|
||||
const [ticket, setTicket] = useState<Ticket | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [cooldownLeftMs, setCooldownLeftMs] = useState(0);
|
||||
const bootstrapped = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (step !== "ticket" || !ticket) {
|
||||
setCooldownLeftMs(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const tick = () => {
|
||||
const elapsed = Date.now() - new Date(ticket.createdAt).getTime();
|
||||
setCooldownLeftMs(
|
||||
Math.max(0, ANOTHER_TICKET_COOLDOWN_MS - elapsed)
|
||||
);
|
||||
};
|
||||
|
||||
tick();
|
||||
const id = window.setInterval(tick, 250);
|
||||
return () => window.clearInterval(id);
|
||||
}, [step, ticket]);
|
||||
|
||||
useEffect(() => {
|
||||
if (bootstrapped.current) return;
|
||||
bootstrapped.current = true;
|
||||
@@ -281,6 +303,9 @@ export default function CustomerTicketFlow({
|
||||
? "Pilih perkhidmatan untuk mendapatkan nombor."
|
||||
: "Imbas QR code di branch anda, atau masukkan kod syarikat di bawah.";
|
||||
|
||||
const canGetAnotherTicket = cooldownLeftMs <= 0;
|
||||
const cooldownSeconds = Math.ceil(cooldownLeftMs / 1000);
|
||||
|
||||
return (
|
||||
<main className="mx-auto flex min-h-full w-full max-w-xl flex-col gap-8 px-6 py-12">
|
||||
<header className="space-y-2">
|
||||
@@ -423,7 +448,7 @@ export default function CustomerTicketFlow({
|
||||
{step === "ticket" && ticket ? (
|
||||
<section className="space-y-6 rounded-md border border-zinc-200 bg-white px-6 py-8 text-center">
|
||||
<p className="text-sm tracking-wide text-zinc-500 uppercase">
|
||||
Nombor tiket anda
|
||||
Nombor giliran anda
|
||||
</p>
|
||||
<p className="text-6xl font-semibold tracking-tight text-zinc-900">
|
||||
{ticket.number}
|
||||
@@ -432,12 +457,36 @@ export default function CustomerTicketFlow({
|
||||
<p>{ticket.service.name}</p>
|
||||
<p>{ticket.branch.name}</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="rounded-md border border-amber-300 bg-amber-50 px-4 py-3 text-left text-sm text-amber-950"
|
||||
role="status"
|
||||
>
|
||||
<p className="font-semibold">Anda sudah mempunyai nombor giliran.</p>
|
||||
<p className="mt-1 text-amber-900">
|
||||
Sila tunggu sehingga nombor anda dipanggil.
|
||||
</p>
|
||||
{!canGetAnotherTicket ? (
|
||||
<p className="mt-2 font-medium tabular-nums text-amber-950">
|
||||
Tiket lain boleh diambil dalam {cooldownSeconds}s
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={resetFlow}
|
||||
className="w-full rounded-md bg-zinc-900 px-4 py-3 text-sm font-medium text-white"
|
||||
disabled={!canGetAnotherTicket || loading}
|
||||
aria-disabled={!canGetAnotherTicket || loading}
|
||||
className={
|
||||
canGetAnotherTicket
|
||||
? "w-full rounded-md border border-zinc-300 bg-white px-4 py-3 text-sm font-medium text-zinc-700 transition hover:border-zinc-500 hover:bg-zinc-50 disabled:opacity-60"
|
||||
: "w-full cursor-not-allowed rounded-md bg-zinc-200 px-4 py-3 text-sm font-medium text-zinc-500"
|
||||
}
|
||||
>
|
||||
Dapatkan tiket lain
|
||||
{canGetAnotherTicket
|
||||
? "Dapatkan nombor giliran lain"
|
||||
: `Tunggu ${cooldownSeconds}s…`}
|
||||
</button>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"dev": "next dev --webpack",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint"
|
||||
|
||||
@@ -15,11 +15,15 @@
|
||||
--bd-error-bg: #5c1a1a;
|
||||
--bd-error-text: #fecaca;
|
||||
|
||||
/* Fluid root: scales 720p → 4K; descendants use em so they follow */
|
||||
font-size: clamp(12px, 0.75vw + 0.55vh, 28px);
|
||||
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 1rem 1.5rem 1rem;
|
||||
padding: 0.75em 1em;
|
||||
background:
|
||||
radial-gradient(ellipse 80% 50% at 10% -10%, rgba(62, 207, 122, 0.12), transparent 55%),
|
||||
radial-gradient(ellipse 60% 40% at 95% 5%, rgba(240, 208, 96, 0.1), transparent 50%),
|
||||
@@ -28,7 +32,7 @@
|
||||
font-family: system-ui, -apple-system, Segoe UI, sans-serif;
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
gap: 0.75rem;
|
||||
gap: 0.6em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -37,28 +41,28 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.branch-display__brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.85rem;
|
||||
gap: 0.85em;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.branch-display__logo {
|
||||
height: clamp(2.5rem, 4.5vw, 3.5rem);
|
||||
height: clamp(2em, 1.8em + 1vw, 4em);
|
||||
width: auto;
|
||||
flex-shrink: 0;
|
||||
border-radius: 0.35rem;
|
||||
border-radius: 0.35em;
|
||||
object-fit: contain;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.branch-display__eyebrow {
|
||||
margin: 0;
|
||||
font-size: 0.75rem;
|
||||
font-size: 0.75em;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--bd-accent);
|
||||
@@ -67,7 +71,7 @@
|
||||
|
||||
.branch-display__title {
|
||||
margin: 0;
|
||||
font-size: clamp(1.4rem, 2.2vw, 2rem);
|
||||
font-size: clamp(1.25em, 2vw, 1.75em);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--bd-text);
|
||||
@@ -76,17 +80,17 @@
|
||||
|
||||
.branch-display__error {
|
||||
margin: 0;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 0.4rem;
|
||||
padding: 0.5em 0.75em;
|
||||
border-radius: 0.4em;
|
||||
background: var(--bd-error-bg);
|
||||
color: var(--bd-error-text);
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.branch-display__main {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(340px, 1.05fr);
|
||||
gap: 0.85rem;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
|
||||
gap: 0.85em;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -94,6 +98,7 @@
|
||||
@media (max-width: 960px) {
|
||||
.branch-display {
|
||||
height: auto;
|
||||
min-height: 100dvh;
|
||||
min-height: 100vh;
|
||||
overflow: auto;
|
||||
grid-template-rows: auto;
|
||||
@@ -110,7 +115,7 @@
|
||||
|
||||
.branch-display__section--gold {
|
||||
flex: none;
|
||||
min-height: 16rem;
|
||||
min-height: 16em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +124,7 @@
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.65rem;
|
||||
gap: 0.75em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -128,12 +133,23 @@
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* Serving block must keep its content height — otherwise cards spill into Giliran */
|
||||
.branch-display__queue-column > .branch-display__section:first-of-type {
|
||||
flex: 0 0 auto;
|
||||
min-height: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.branch-display__section--waiting {
|
||||
flex: 0 1 auto;
|
||||
max-height: 28%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.branch-display__section--gold {
|
||||
@@ -141,11 +157,12 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.branch-display__section-title {
|
||||
margin: 0 0 0.45rem;
|
||||
font-size: 0.85rem;
|
||||
margin: 0 0 0.45em;
|
||||
font-size: 1.05em;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
@@ -154,16 +171,16 @@
|
||||
|
||||
.branch-display__stations {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 0.55rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 10em), 1fr));
|
||||
gap: 0.7em;
|
||||
}
|
||||
|
||||
.branch-display__station-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
padding: 0.65rem 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
gap: 0.3em;
|
||||
padding: 0.9em 0.65em;
|
||||
border-radius: 0.5em;
|
||||
background: linear-gradient(160deg, var(--bd-surface-raised) 0%, var(--bd-surface) 100%);
|
||||
border: 1px solid var(--bd-border);
|
||||
box-shadow: inset 0 1px 0 rgba(62, 207, 122, 0.08);
|
||||
@@ -172,18 +189,38 @@
|
||||
|
||||
.branch-display__station-name {
|
||||
margin: 0;
|
||||
font-size: 0.8rem;
|
||||
font-size: 1.1em;
|
||||
color: var(--bd-muted);
|
||||
}
|
||||
|
||||
.branch-display__ticket-number {
|
||||
margin: 0;
|
||||
font-size: clamp(1.75rem, 3.2vw, 2.75rem);
|
||||
font-size: clamp(1.75em, min(4.2vw, 8vh), 5.5em);
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.03em;
|
||||
color: var(--bd-accent-bright);
|
||||
text-shadow: 0 0 24px rgba(255, 229, 102, 0.25);
|
||||
text-shadow: 0 0 1.5em rgba(255, 229, 102, 0.25);
|
||||
}
|
||||
|
||||
/* Short TVs (e.g. 720p): tighten serving cards so sections don't collide */
|
||||
@media (max-height: 800px) {
|
||||
.branch-display__station-card {
|
||||
padding: 0.55em 0.5em;
|
||||
gap: 0.15em;
|
||||
}
|
||||
|
||||
.branch-display__ticket-number {
|
||||
font-size: clamp(1.5em, min(3.6vw, 7vh), 3.25em);
|
||||
}
|
||||
|
||||
.branch-display__section-title {
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
||||
.branch-display__section--waiting {
|
||||
max-height: 24%;
|
||||
}
|
||||
}
|
||||
|
||||
.branch-display__ticket-number--idle {
|
||||
@@ -193,7 +230,7 @@
|
||||
|
||||
.branch-display__station-service {
|
||||
margin: 0;
|
||||
font-size: 0.75rem;
|
||||
font-size: 1em;
|
||||
color: var(--bd-muted-strong);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
@@ -204,8 +241,8 @@
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.4rem;
|
||||
gap: 0.75em;
|
||||
margin-bottom: 0.4em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -215,14 +252,14 @@
|
||||
|
||||
.branch-display__waiting-count {
|
||||
margin: 0;
|
||||
font-size: 0.85rem;
|
||||
font-size: 1.05em;
|
||||
color: var(--bd-accent);
|
||||
}
|
||||
|
||||
.branch-display__empty {
|
||||
margin: 0;
|
||||
color: var(--bd-muted);
|
||||
font-size: 0.9rem;
|
||||
font-size: 1.05em;
|
||||
}
|
||||
|
||||
.branch-display__waiting-list {
|
||||
@@ -231,7 +268,7 @@
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
gap: 0.45em;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
@@ -241,24 +278,24 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
padding: 0.4rem 0.7rem;
|
||||
border-radius: 0.4rem;
|
||||
gap: 0.75em;
|
||||
padding: 0.55em 0.85em;
|
||||
border-radius: 0.4em;
|
||||
background: var(--bd-surface);
|
||||
border: 1px solid var(--bd-border);
|
||||
border-left: 3px solid var(--bd-green);
|
||||
border-left: 0.2em solid var(--bd-green);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.branch-display__waiting-number {
|
||||
font-size: clamp(1.1rem, 1.8vw, 1.5rem);
|
||||
font-size: clamp(1.35em, 2.4vw, 2.5em);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--bd-accent);
|
||||
}
|
||||
|
||||
.branch-display__waiting-service {
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.85em;
|
||||
color: var(--bd-muted-strong);
|
||||
}
|
||||
|
||||
@@ -266,8 +303,8 @@
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
padding: 0.65rem;
|
||||
border-radius: 0.6rem;
|
||||
padding: 0.65em;
|
||||
border-radius: 0.6em;
|
||||
background: linear-gradient(160deg, var(--bd-surface-raised) 0%, var(--bd-surface) 100%);
|
||||
border: 1px solid var(--bd-border);
|
||||
box-shadow: inset 0 1px 0 rgba(240, 208, 96, 0.06);
|
||||
@@ -280,8 +317,8 @@
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.4rem;
|
||||
gap: 0.75em;
|
||||
margin-bottom: 0.4em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -294,7 +331,7 @@
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
border-radius: 0.4rem;
|
||||
border-radius: 0.4em;
|
||||
overflow: hidden;
|
||||
background: var(--bd-bg);
|
||||
border: 1px solid var(--bd-border);
|
||||
@@ -309,8 +346,8 @@
|
||||
}
|
||||
|
||||
.branch-display__ad-caption {
|
||||
margin: 0.35rem 0 0;
|
||||
font-size: 0.8rem;
|
||||
margin: 0.35em 0 0;
|
||||
font-size: 0.8em;
|
||||
color: var(--bd-muted-strong);
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
@@ -320,7 +357,7 @@
|
||||
}
|
||||
|
||||
.branch-display__section--gold .branch-display__waiting-header {
|
||||
margin-bottom: 0.35rem;
|
||||
margin-bottom: 0.35em;
|
||||
}
|
||||
|
||||
.branch-display__gold-table-wrap {
|
||||
@@ -328,7 +365,7 @@
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
border-radius: 0.5rem;
|
||||
border-radius: 0.5em;
|
||||
border: 1px solid var(--bd-border);
|
||||
background: var(--bd-surface);
|
||||
scrollbar-width: none;
|
||||
@@ -341,19 +378,19 @@
|
||||
.branch-display__gold-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.9rem;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
.branch-display__gold-table th,
|
||||
.branch-display__gold-table td {
|
||||
padding: 0.45rem 0.65rem;
|
||||
padding: 0.55em 0.75em;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--bd-border);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.branch-display__gold-table th {
|
||||
font-size: 0.7rem;
|
||||
font-size: 0.72em;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: var(--bd-accent);
|
||||
|
||||
@@ -236,7 +236,7 @@ export default function BranchDisplayPage() {
|
||||
{ticket ? ticket.number : '—'}
|
||||
</p>
|
||||
<p className="branch-display__station-service">
|
||||
{ticket?.service?.name ?? 'Waiting for next'}
|
||||
{ticket?.service?.name ?? 'Menunggu nombor giliran berikutnya'}
|
||||
</p>
|
||||
</article>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user