diff --git a/src/features/devices/components/devices-dialogs.tsx b/src/features/devices/components/devices-dialogs.tsx
new file mode 100644
index 0000000..b5b60e0
--- /dev/null
+++ b/src/features/devices/components/devices-dialogs.tsx
@@ -0,0 +1,18 @@
+import { RoleBasedRender } from '@/components/role-based/role-based-render'
+import { DeviceDeleteDialog } from './device-delete-dialog'
+import { DeviceEditDialog } from './device-edit-dialog'
+import { PortSettingsModal } from './port-settings-modal'
+import { DeviceBulkCreateDialog } from './device-bulk-create-dialog'
+
+export function DevicesDialogs() {
+ return (
+ <>
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/src/features/devices/components/devices-primary-buttons.tsx b/src/features/devices/components/devices-primary-buttons.tsx
new file mode 100644
index 0000000..a1d9eef
--- /dev/null
+++ b/src/features/devices/components/devices-primary-buttons.tsx
@@ -0,0 +1,28 @@
+import { Button } from '@/components/ui/button'
+import { IconPlus, IconUpload } from '@tabler/icons-react'
+import { useNavigate } from '@tanstack/react-router'
+import { useDevicesContext } from '../context/devices-context'
+
+export function DevicesPrimaryButtons() {
+ const navigate = useNavigate()
+ const { setIsBulkCreateDialogOpen } = useDevicesContext()
+
+ const handleCreateDevice = () => {
+ navigate({ to: '/devices/create' })
+ }
+
+ const handleBulkCreate = () => {
+ setIsBulkCreateDialogOpen(true)
+ }
+
+ return (
+
+
+
+
+ )
+}
diff --git a/src/features/devices/context/devices-context.tsx b/src/features/devices/context/devices-context.tsx
new file mode 100644
index 0000000..5bbb1ee
--- /dev/null
+++ b/src/features/devices/context/devices-context.tsx
@@ -0,0 +1,265 @@
+import React, { createContext, useContext, useState } from 'react'
+import { DeviceComplete, DeviceFilters, DeviceSorting } from '../types/device'
+
+interface DevicesContextType {
+ // UI State
+ selectedDevices: string[]
+ setSelectedDevices: (devices: string[]) => void
+
+ // Dialog states
+ isEditDialogOpen: boolean
+ setIsEditDialogOpen: (open: boolean) => void
+
+ isDeleteDialogOpen: boolean
+ setIsDeleteDialogOpen: (open: boolean) => void
+
+ isPortSettingsModalOpen: boolean
+ setIsPortSettingsModalOpen: (open: boolean) => void
+
+ isBulkCreateDialogOpen: boolean
+ setIsBulkCreateDialogOpen: (open: boolean) => void
+
+ // Current device for edit/delete
+ currentDevice: DeviceComplete | null
+ setCurrentDevice: (device: DeviceComplete | null) => void
+
+ // Frontend filtering and sorting
+ filters: DeviceFilters
+ setFilters: (filters: DeviceFilters) => void
+
+ sorting: DeviceSorting | null
+ setSorting: (sorting: DeviceSorting | null) => void
+
+ // Pagination (frontend)
+ currentPage: number
+ setCurrentPage: (page: number) => void
+
+ pageSize: number
+ setPageSize: (size: number) => void
+
+ // Region filtering state
+ selectedProvince: string
+ setSelectedProvince: (province: string) => void
+ selectedCity: string
+ setSelectedCity: (city: string) => void
+ selectedDistrict: string
+ setSelectedDistrict: (district: string) => void
+
+ // Actions
+ openEditDialog: (device: DeviceComplete) => void
+ openDeleteDialog: (device: DeviceComplete) => void
+ closeAllDialogs: () => void
+
+ resetFilters: () => void
+
+ // Bulk operations
+ isBulkMode: boolean
+ setIsBulkMode: (mode: boolean) => void
+ toggleDeviceSelection: (deviceId: string) => void
+ selectAllDevices: (deviceIds: string[]) => void
+ clearSelection: () => void
+ bulkDeleteSelected: () => void
+
+ // Settings Port
+ portSettingsDevice: DeviceComplete | null
+ isPortSettingsOpen: boolean
+ setIsPortSettingsOpen: (open: boolean) => void
+ openPortSettingsDialog: (device: DeviceComplete) => void
+
+ // Region filter handlers
+ handleProvinceChange: (provinceId: string, provinceName?: string) => void
+ handleCityChange: (cityId: string, cityName?: string) => void
+ handleDistrictChange: (districtId: string, districtName?: string) => void
+}
+
+const DevicesContext = createContext(undefined)
+
+interface DevicesProviderProps {
+ children: React.ReactNode
+}
+
+const initialFilters: DeviceFilters = {
+ search: '',
+ status: '',
+ device_type: '',
+ province: '',
+ city: '',
+ district: '',
+}
+
+export default function DevicesProvider({ children }: DevicesProviderProps) {
+ // UI State
+ const [selectedDevices, setSelectedDevices] = useState([])
+
+ // Dialog states
+ const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
+ const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
+ const [isBulkCreateDialogOpen, setIsBulkCreateDialogOpen] = useState(false)
+
+ // Current device
+ const [currentDevice, setCurrentDevice] = useState(null)
+
+ // Frontend filtering and sorting
+ const [filters, setFilters] = useState(initialFilters)
+ const [sorting, setSorting] = useState(null)
+
+ // Pagination
+ const [currentPage, setCurrentPage] = useState(1)
+ const [pageSize, setPageSize] = useState(10)
+
+ // Region filtering state
+ const [selectedProvince, setSelectedProvince] = useState('')
+ const [selectedCity, setSelectedCity] = useState('')
+ const [selectedDistrict, setSelectedDistrict] = useState('')
+
+ // Bulk operations state
+ const [isBulkMode, setIsBulkMode] = useState(false)
+
+ // Settings port
+ const [portSettingsDevice, setPortSettingsDevice] = useState(null)
+ const [isPortSettingsOpen, setIsPortSettingsOpen] = useState(false)
+ const [isPortSettingsModalOpen, setIsPortSettingsModalOpen] = useState(false)
+
+ // Actions
+ const openEditDialog = (device: DeviceComplete) => {
+ setCurrentDevice(device)
+ setIsEditDialogOpen(true)
+ }
+
+ const openDeleteDialog = (device: DeviceComplete) => {
+ setCurrentDevice(device)
+ setIsDeleteDialogOpen(true)
+ }
+
+ const closeAllDialogs = () => {
+ setIsEditDialogOpen(false)
+ setIsDeleteDialogOpen(false)
+ setCurrentDevice(null)
+ }
+
+ const resetFilters = () => {
+ setFilters(initialFilters)
+ setSorting(null)
+ setCurrentPage(1)
+ }
+
+ const openPortSettingsDialog = (device: DeviceComplete) => {
+ setPortSettingsDevice(device)
+ setIsPortSettingsOpen(true)
+ }
+
+ // Region filter handlers
+ const handleProvinceChange = (provinceId: string, provinceName?: string) => {
+ setSelectedProvince(provinceId) // ID for API calls
+ setSelectedCity('')
+ setSelectedDistrict('')
+ setFilters(prev => ({
+ ...prev,
+ province: provinceName || provinceId, // Name for filtering
+ city: '',
+ district: ''
+ }))
+ }
+
+ const handleCityChange = (cityId: string, cityName?: string) => {
+ setSelectedCity(cityId) // ID for API calls
+ setSelectedDistrict('')
+ setFilters(prev => ({
+ ...prev,
+ city: cityName || cityId, // Name for filtering
+ district: ''
+ }))
+ }
+
+ const handleDistrictChange = (districtId: string, districtName?: string) => {
+ setSelectedDistrict(districtId) // ID for API calls
+ setFilters(prev => ({
+ ...prev,
+ district: districtName || districtId // Name for filtering
+ }))
+ }
+
+ // Bulk operations functions
+ const toggleDeviceSelection = (deviceId: string) => {
+ setSelectedDevices(prev =>
+ prev.includes(deviceId)
+ ? prev.filter(id => id !== deviceId)
+ : [...prev, deviceId]
+ )
+ }
+
+ const selectAllDevices = (deviceIds: string[]) => {
+ setSelectedDevices(deviceIds)
+ }
+
+ const clearSelection = () => {
+ setSelectedDevices([])
+ setIsBulkMode(false)
+ }
+
+ const bulkDeleteSelected = () => {
+ // This will be handled by the bulk delete hook
+ // The actual deletion logic is in the component that calls this
+ console.log('Bulk delete selected devices:', selectedDevices)
+ }
+
+ const value: DevicesContextType = {
+ selectedDevices,
+ setSelectedDevices,
+ isEditDialogOpen,
+ setIsEditDialogOpen,
+ isDeleteDialogOpen,
+ setIsDeleteDialogOpen,
+ isPortSettingsModalOpen,
+ setIsPortSettingsModalOpen,
+ isBulkCreateDialogOpen,
+ setIsBulkCreateDialogOpen,
+ currentDevice,
+ setCurrentDevice,
+ filters,
+ setFilters,
+ sorting,
+ setSorting,
+ currentPage,
+ setCurrentPage,
+ pageSize,
+ setPageSize,
+ selectedProvince,
+ setSelectedProvince,
+ selectedCity,
+ setSelectedCity,
+ selectedDistrict,
+ setSelectedDistrict,
+ openEditDialog,
+ openDeleteDialog,
+ closeAllDialogs,
+ resetFilters,
+ portSettingsDevice,
+ isPortSettingsOpen,
+ setIsPortSettingsOpen,
+ openPortSettingsDialog,
+ handleProvinceChange,
+ handleCityChange,
+ handleDistrictChange,
+ isBulkMode,
+ setIsBulkMode,
+ toggleDeviceSelection,
+ selectAllDevices,
+ clearSelection,
+ bulkDeleteSelected
+ }
+
+ return (
+
+ {children}
+
+ )
+}
+
+export const useDevicesContext = () => {
+ const context = useContext(DevicesContext)
+ if (context === undefined) {
+ throw new Error('useDevicesContext must be used within a DevicesProvider')
+ }
+ return context
+}
diff --git a/src/features/devices/hooks/use-devices.ts b/src/features/devices/hooks/use-devices.ts
new file mode 100644
index 0000000..f91592e
--- /dev/null
+++ b/src/features/devices/hooks/use-devices.ts
@@ -0,0 +1,164 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
+import { toast } from 'sonner'
+import { devicesService } from '../services/devices-service'
+import { DevicePortsUpdateRequest } from '../types/device'
+
+export const useDevices = ({
+ type = 'all',
+}: { type?: 'otb' | 'closure' | 'odp' | 'all' } = {}) => {
+ return useQuery({
+ queryKey: ['devices'],
+ queryFn: devicesService.getDevices,
+ select: (data) => {
+ if (type === 'otb') {
+ return data.data.filter(d => d.device_type.toLowerCase() === 'otb') || []
+ }
+ if (type === 'closure') {
+ return data.data.filter(d => d.device_type.toLowerCase() === 'closure') || []
+ }
+ if (type === 'odp') {
+ return data.data.filter(d => d.device_type.toLowerCase() === 'odp') || []
+ }
+ return data.data || []
+ },
+ })
+}
+
+export const useDevice = (id: string) => {
+ return useQuery({
+ queryKey: ['device', id],
+ queryFn: () => devicesService.getDevice(id),
+ select: (data) => data.data,
+ enabled: !!id,
+ })
+}
+
+export const useDeviceWithPorts = (id: string) => {
+ return useQuery({
+ queryKey: ['device-with-ports', id],
+ queryFn: () => devicesService.getDeviceWithPorts(id),
+ select: (data) => data.data,
+ enabled: !!id,
+ })
+}
+
+export const useCreateDevice = () => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (data: FormData) => devicesService.createDevice(data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['devices'] })
+ toast.success('Device created successfully')
+ },
+ onError: (error: any) => {
+ toast.error(error.response?.data?.status?.description || 'Failed to create device')
+ },
+ })
+}
+
+export const useUpdateDevice = () => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: ({ id, formData }: { id: string; formData: FormData }) => devicesService.updateDevice(id, formData),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['devices'] })
+ toast.success('Device updated successfully')
+ },
+ onError: (error: any) => {
+ toast.error(error.response?.data?.status?.description || 'Failed to update device')
+ },
+ })
+}
+
+export const useDeleteDeviceImage = () => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: ({ deviceId, imageName }: { deviceId: string; imageName: string }) =>
+ devicesService.deleteDeviceImage(deviceId, imageName),
+ onSuccess: (_, variables) => {
+ queryClient.invalidateQueries({ queryKey: ['devices'] })
+ queryClient.invalidateQueries({ queryKey: ['device', variables.deviceId] })
+ queryClient.invalidateQueries({ queryKey: ['device-with-ports', variables.deviceId] })
+ toast.success('Image deleted successfully')
+ },
+ onError: (error: any) => {
+ toast.error(error.response?.data?.status?.description || 'Failed to delete image')
+ },
+ })
+}
+
+// Updated hook to use the new assign-customer endpoint
+export const useAssignCustomersToPorts = () => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (data: DevicePortsUpdateRequest) => devicesService.assignCustomersToPorts(data),
+ onSuccess: (_, variables) => {
+ queryClient.invalidateQueries({ queryKey: ['devices'] })
+ queryClient.invalidateQueries({ queryKey: ['device-with-ports', variables.device_id] })
+ toast.success('Customer assignments updated successfully')
+ },
+ onError: (error: any) => {
+ toast.error(error.response?.data?.status?.description || 'Failed to update customer assignments')
+ },
+ })
+}
+
+// Keep the old hook name for backward compatibility
+export const useUpdateDevicePorts = useAssignCustomersToPorts
+
+export const useDeleteDevice = () => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (id: string) => devicesService.deleteDevice(id),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['devices'] })
+ toast.success('Device deleted successfully')
+ },
+ onError: (error: any) => {
+ toast.error(error.response?.data?.status?.description || 'Failed to delete device')
+ },
+ })
+}
+
+export const useBulkDeleteDevices = () => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (ids: string[]) => devicesService.bulkDeleteDevices(ids),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['devices'] })
+ toast.success('Devices deleted successfully')
+ },
+ onError: (error: any) => {
+ toast.error(error.response?.data?.status?.description || 'Failed to delete devices')
+ },
+ })
+}
+
+export const useBulkCreateDevices = () => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (devices: any[]) => devicesService.bulkCreateDevices(devices),
+ onSuccess: (data) => {
+ queryClient.invalidateQueries({ queryKey: ['devices'] })
+ const successCount = data.data?.success_count || 0
+ const failureCount = data.data?.failure_count || 0
+
+ if (failureCount > 0) {
+ toast.warning(`${successCount} devices created successfully, ${failureCount} failed`)
+ } else {
+ toast.success(`${successCount} devices created successfully`)
+ }
+ },
+ onError: (error: any) => {
+ toast.error(error.response?.data?.status?.description || 'Failed to create devices')
+ },
+ })
+}
diff --git a/src/features/devices/index.tsx b/src/features/devices/index.tsx
new file mode 100644
index 0000000..7deb2dc
--- /dev/null
+++ b/src/features/devices/index.tsx
@@ -0,0 +1,122 @@
+import { useState, useMemo } from 'react'
+import { Header } from '@/components/layout/header'
+import { Main } from '@/components/layout/main'
+import { ProfileDropdown } from '@/components/profile-dropdown'
+import { Search } from '@/components/search'
+import { ThemeSwitch } from '@/components/theme-switch'
+import { columns } from './components/columns'
+import { DataTable } from './components/data-table'
+import { DevicesDialogs } from './components/devices-dialogs'
+import { DevicesPrimaryButtons } from './components/devices-primary-buttons'
+import { ViewSwitcher } from './components/view-switcher'
+import { DevicesCards } from './components/devices-cards'
+import { DevicesRegionFilter } from './components/devices-region-filter'
+import DevicesProvider, { useDevicesContext } from './context/devices-context'
+import { useDevices } from './hooks/use-devices'
+import { Skeleton } from '@/components/ui/skeleton'
+import { useEffect } from 'react'
+import { handleServerError } from '@/utils/handle-server-error'
+
+
+function DevicesContent() {
+ const { data: devices, isLoading, error } = useDevices()
+ const { filters } = useDevicesContext()
+ const [view, setView] = useState<'table' | 'cards'>('table')
+
+ // Filter devices based on region filters
+ const filteredDevices = useMemo(() => {
+ if (!devices) return []
+
+ return devices.filter((device) => {
+ const matchesProvince = !filters.province || device.province === filters.province
+ const matchesCity = !filters.city || device.city === filters.city
+ const matchesDistrict = !filters.district || device.district === filters.district
+
+ return matchesProvince && matchesCity && matchesDistrict
+ })
+ }, [devices, filters.province, filters.city, filters.district])
+
+ useEffect(() => {
+ if (error) {
+ handleServerError(error)
+ }
+ }, [error])
+
+ const renderSkeletons = () => {
+ if (view === 'table') {
+ return (
+
+ )
+ } else {
+ return (
+
+ {Array.from({ length: 8 }).map((_, i) => (
+
+ ))}
+
+ )
+ }
+ }
+
+ return (
+ <>
+
+
+
+
+
+
Perangkat
+
+ Berikut adalah daftar perangkat Anda!
+
+
+
+
+
+
+
+
+ {/* Region Filter */}
+
+
+
+
+
+ {isLoading ? (
+ renderSkeletons()
+ ) : view === 'table' ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ >
+ )
+}
+
+export default function Devices() {
+ return (
+
+
+
+ )
+}
diff --git a/src/features/devices/services/devices-service.ts b/src/features/devices/services/devices-service.ts
new file mode 100644
index 0000000..a60b4ae
--- /dev/null
+++ b/src/features/devices/services/devices-service.ts
@@ -0,0 +1,85 @@
+import { apiClient } from '@/lib/api'
+import { ApiResponse } from '@/lib/api/types'
+import {
+ DeviceResponse,
+ DevicePortsUpdateRequest,
+ DeviceComplete,
+ DevicesWithoutConnectionsResponse,
+ DevicesWithoutTowersResponse
+} from '../types/device'
+
+export const devicesService = {
+ // Get all devices (no query params - get everything)
+ getDevices: async (): Promise> => {
+ return apiClient.get('/device-details')
+ },
+
+ // Get single device by ID
+ getDevice: async (id: string): Promise> => {
+ return apiClient.get(`/device-details/${id}`)
+ },
+
+ // Create new device
+ createDevice: async (data: FormData): Promise> => {
+ return apiClient.postFormData('/devices', data)
+ },
+
+ // Update device
+ updateDevice: async (id: string, data: FormData): Promise> => {
+ return apiClient.putFormData(`/device-details/${id}`, data)
+ },
+
+ // Delete device
+ deleteDevice: async (id: string): Promise> => {
+ return apiClient.delete(`/devices/${id}`)
+ },
+
+ // Delete device image - NEW
+ deleteDeviceImage: async (deviceId: string, imageName: string): Promise> => {
+ return apiClient.delete(`/device-details/${deviceId}/images/${imageName}`)
+ },
+
+ // Bulk delete devices
+ bulkDeleteDevices: async (ids: string[]): Promise> => {
+ return apiClient.post('/devices/bulk-delete', { ids })
+ },
+
+ // Bulk create devices
+ bulkCreateDevices: async (devices: any[]): Promise> => {
+ return apiClient.post<{ success_count: number; failure_count: number; errors?: any[] }>('/devices/bulk-create', { devices })
+ },
+
+ // Assign customers to device ports - new endpoint
+ assignCustomersToPorts: async (data: DevicePortsUpdateRequest): Promise> => {
+ const payload = data.ports.map(port => ({
+ new_customer_name: port.customer_name,
+ port_number: port.port_number,
+ bandwidth: port.bandwidth,
+ is_occupied: port.is_occupied,
+ status: port.status,
+ }))
+
+ return apiClient.put(`/device-details/${data.device_id}/bulk-update-customers-by-port`, { updates: payload })
+ },
+
+ // Get device with port assignments - uses device-details endpoint
+ getDeviceWithPorts: async (deviceId: string): Promise> => {
+ return apiClient.get(`/device-details/${deviceId}`)
+ },
+
+ // Get devices without connections (not used by fishbone or backbone)
+ getDevicesWithoutConnections: async (deviceTypes: string[]): Promise> => {
+ const params = new URLSearchParams()
+ params.append('device_types', deviceTypes.join(','))
+
+ return apiClient.get(`/device-details/without-connections?${params.toString()}`)
+ },
+
+ // Get devices without towers
+ getDevicesWithoutTowers: async (deviceTypes: string[]): Promise> => {
+ const params = new URLSearchParams()
+ params.append('device_types', deviceTypes.join(','))
+
+ return apiClient.get(`/device-details/without-towers?${params.toString()}`)
+ },
+}
diff --git a/src/features/devices/types/device.ts b/src/features/devices/types/device.ts
new file mode 100644
index 0000000..bbca5f5
--- /dev/null
+++ b/src/features/devices/types/device.ts
@@ -0,0 +1,168 @@
+import { Backbone } from '@/features/backbones/types/backbone'
+import { Fishbone } from '@/features/fishbones/types/fishbone'
+import { Olt } from '@/features/olts/types/olt'
+import { Tower } from '@/features/towers/types/tower'
+
+export interface Device {
+ id: string
+ device_code: string
+ device_type: string
+ longitude: number
+ latitude: number
+ address: string
+ port_amount: number
+ total_used_port: number
+ province?: string
+ city?: string
+ district?: string
+ status: 'active' | 'inactive' | 'maintenance'
+ created_at: string
+}
+
+export interface PortAssignment {
+ port_number: number
+ customer_name: string | null
+ is_occupied: boolean
+ status: "dyingGasp" | "los" | "on" | "off"
+ bandwidth: string | null
+}
+
+export interface DeviceComplete {
+ id: string
+ olt: Olt | null
+ device_code: string
+ device_type: string
+ address: string
+ longitude: number
+ latitude: number
+ status: 'active' | 'inactive' | 'maintenance'
+ port_amount: number
+ port_used: number
+ port_available: number
+ customer_names: string[] | null
+ port_assignments: PortAssignment[]
+ province: string | null
+ city: string | null
+ district: string | null
+ image_urls: string[]
+ backbones: (Pick & {
+ is_start_device: boolean
+ connected_to: string
+ })[]
+ fishbones: (Pick & {
+ is_start_device: boolean
+ connected_to: string
+ })[]
+ towers: (Omit<
+ Tower,
+ 'latitude' | 'longitude' | 'device_code' | 'address' | 'created_at'
+ > & { distance_km: number })[]
+ created_at: string
+ updated_at: string
+}
+
+export interface DeviceCreateRequest {
+ device_code: string
+ device_type: string
+ longitude: number
+ latitude: number
+ port_amount?: number | null
+ status: 'active' | 'inactive' | 'maintenance'
+ province?: string
+ city?: string
+ district?: string
+ image_urls?: File[]
+}
+
+export interface DeviceUpdateRequest extends Partial {
+ id: string
+}
+
+export interface DevicesListResponse {
+ devices: Device[]
+ // Remove pagination fields since we get all data
+}
+
+export interface DeviceResponse {
+ device: Device
+}
+
+// Frontend filtering state
+export interface DeviceFilters {
+ search: string
+ status: string
+ device_type: string
+ port_status?: string // Add this new field
+ province?: string
+ city?: string
+ district?: string
+}
+
+export interface DeviceSorting {
+ field: keyof Device
+ direction: 'asc' | 'desc'
+}
+
+export type PortAssignmentStatus = 'no_customer' | 'in_use'
+
+// Add connection status from API
+export type PortConnectionStatus = 'dyingGasp' | 'los' | 'on' | 'off'
+
+export interface Port {
+ port_number: number
+ assignment_status: PortAssignmentStatus
+ connection_status?: PortConnectionStatus
+ customer_name?: string
+ is_occupied: boolean
+ bandwidth?: string | null
+}
+
+export interface DevicePort extends Device {
+ ports?: Port[]
+}
+export interface DevicePortAssignmentRequest {
+ customer_name: string | null
+ port_number: number
+ is_occupied: boolean
+ status: "dyingGasp" | "los" | "on" | "off"
+ bandwidth: string | null
+}
+
+export interface DevicePortsUpdateRequest {
+ device_id: string
+ ports: DevicePortAssignmentRequest[]
+}
+
+export interface DeviceBreakdown {
+ closure: {
+ count: number
+ devices: DeviceComplete[]
+ }
+ otb: {
+ count: number
+ devices: DeviceComplete[]
+ }
+}
+
+export interface DevicesWithoutConnectionsFilter {
+ criteria: string
+ device_types: string[]
+}
+
+export interface DevicesWithoutConnectionsResponse {
+ breakdown: DeviceBreakdown
+ devices: DeviceComplete[]
+ filter: DevicesWithoutConnectionsFilter
+ total: number
+}
+
+export interface DevicesWithoutTowersFilter {
+ criteria: string
+ device_types: string[]
+}
+
+export interface DevicesWithoutTowersResponse {
+ devices: DeviceComplete[]
+ filter: DevicesWithoutTowersFilter
+ total: number
+}
\ No newline at end of file