Repository: CP-RektMart/pic-me-pls-frontend
Pull request #75: Feature/pic 176 177 fe implement/integrate attch drive dialog
Author: login: tin2003tin
Created: 2025-03-24T15:46:46Z | Merged: 2025-03-25T09:43:56Z

# Photographer Quotation Full Page
![image](https://github.com/user-attachments/assets/30fdadea-544f-48ff-80b1-3012c1c196e8)
![image](https://github.com/user-attachments/assets/1f3652dc-60b3-4217-9c72-d84e2c0d2fd7)
![image](https://github.com/user-attachments/assets/9ad06f3b-120a-44f2-bfb9-958057508c31)

# Photographer Quotation Small Page
![image](https://github.com/user-attachments/assets/9a8d9a24-f526-4cc2-82cd-ffaab31cc9d7)
![image](https://github.com/user-attachments/assets/046bba19-aebd-42d4-bfee-a84afeff6c35)

# Customer Qutatiion Page 
![image](https://github.com/user-attachments/assets/89f64ce0-dd55-4801-a92b-ba2a32024152)
![image](https://github.com/user-attachments/assets/b9b0df35-29aa-4e59-a5b3-e7a5762dc554)
![image](https://github.com/user-attachments/assets/3f4183a6-49a8-478c-b5e0-55420cbcd550)

/ UX UI implemented 
/ integrated with submite-preview api and get-preview by GET quotation-by-id
/ adjust style like border for container, margin
/ coding style is so bad
/ css is so bad
/ wait for refatoring code


Diff:
diff --git a/src/actions/chat/get-chat.ts b/src/actions/chat/get-chat.ts
index 0e6c10c8..21118f6d 100644
--- a/src/actions/chat/get-chat.ts
+++ b/src/actions/chat/get-chat.ts
@@ -40,6 +40,7 @@ const mockQuotation: Quotation = {
   to: new Date(new Date().setDate(new Date().getDate() + 1)),
   description: 'Sample quotation description',
   price: 500,
+  photographerPictureUrl: '',
 }
 
 export const mockChats: Chat[] = [
diff --git a/src/actions/quotation/create-preview.ts b/src/actions/quotation/create-preview.ts
new file mode 100644
index 00000000..3507c984
--- /dev/null
+++ b/src/actions/quotation/create-preview.ts
@@ -0,0 +1,18 @@
+'use server'
+
+import { client } from '@/api/client'
+
+interface AddPreviewAction {
+  quotationId: number
+  link: string
+}
+
+export async function createPreview({ quotationId, link }: AddPreviewAction) {
+  await client.POST('/api/v1/photographer/quotations/{id}/preview', {
+    params: { path: { id: quotationId } },
+    body: {
+      quotationId: quotationId,
+      link: link,
+    },
+  })
+}
diff --git a/src/actions/quotation/get-preview.ts b/src/actions/quotation/get-preview.ts
new file mode 100644
index 00000000..bd095505
--- /dev/null
+++ b/src/actions/quotation/get-preview.ts
@@ -0,0 +1,16 @@
+'use server'
+
+import { client } from '@/api/client'
+import { PreviewList } from '@/types/quotation'
+
+export async function getPreviewById(
+  quotationId: number
+): Promise<PreviewList> {
+  const { response, data } = await client.GET('/api/v1/quotations/{id}', {
+    params: { path: { id: quotationId } },
+  })
+  if (!response.ok || !data?.result) {
+    return []
+  }
+  return data?.result?.previews
+}
diff --git a/src/actions/quotation/get-quotations.ts b/src/actions/quotation/get-quotations.ts
index e11268f1..45c171a1 100644
--- a/src/actions/quotation/get-quotations.ts
+++ b/src/actions/quotation/get-quotations.ts
@@ -14,6 +14,7 @@ export interface Quotation {
   to: Date
   description: string
   price: number
+  photographerPictureUrl: string
 }
 
 export async function getQuotations(): Promise<Quotation[]> {
@@ -40,6 +41,7 @@ export async function getQuotations(): Promise<Quotation[]> {
         to: new Date(quotation.toDate || new Date()),
         description: quotation.description || '',
         price: quotation.price || 0,
+        photographerPictureUrl: quotation.photographer?.profilePictureUrl || '',
       })
     )
     .sort((a, b) => a.quotationID - b.quotationID)
diff --git a/src/api/schema.d.ts b/src/api/schema.d.ts
index 509c05a3..c9658024 100644
--- a/src/api/schema.d.ts
+++ b/src/api/schema.d.ts
@@ -490,7 +490,7 @@ export interface paths {
     patch?: never
     trace?: never
   }
-  '/api/v1/customer/packages/:packageID/reviews': {
+  '/api/v1/customer/packages/{packageID}/reviews': {
     parameters: {
       query?: never
       header?: never
@@ -778,6 +778,69 @@ export interface paths {
     }
     trace?: never
   }
+  '/api/v1/customer/quotations/{id}/review': {
+    parameters: {
+      query?: never
+      header?: never
+      path?: never
+      cookie?: never
+    }
+    get?: never
+    put?: never
+    /**
+     * Create a review
+     * @description Create a review for a quotation.
+     */
+    post: {
+      parameters: {
+        query?: never
+        header?: never
+        path: {
+          /** @description Quotation ID */
+          id: string
+        }
+        cookie?: never
+      }
+      /** @description Review details */
+      requestBody: {
+        content: {
+          'application/json': components['schemas']['dto.CreateReviewRequest']
+        }
+      }
+      responses: {
+        /** @description Review created successfully */
+        204: {
+          headers: {
+            [name: string]: unknown
+          }
+          content?: never
+        }
+        /** @description Bad Request */
+        400: {
+          headers: {
+            [name: string]: unknown
+          }
+          content: {
+            'application/json': components['schemas']['dto.HttpError']
+          }
+        }
+        /** @description Internal Server Error */
+        500: {
+          headers: {
+            [name: string]: unknown
+          }
+          content: {
+            'application/json': components['schemas']['dto.HttpError']
+          }
+        }
+      }
+    }
+    delete?: never
+    options?: never
+    head?: never
+    patch?: never
+    trace?: never
+  }
   '/api/v1/customers/{id}': {
     parameters: {
       query?: never
@@ -1942,15 +2005,11 @@ export interface paths {
     }
     get?: never
     put?: never
-    post?: never
-    delete?: never
-    options?: never
-    head?: never
     /**
      * submit preview photo
      * @description submit preview photo and set status to submitted
      */
-    patch: {
+    post: {
       parameters: {
         query?: never
         header?: never
@@ -1960,10 +2019,10 @@ export interface paths {
         }
         cookie?: never
       }
-      /** @description link */
+      /** @description preview details */
       requestBody: {
         content: {
-          'text/plain': string
+          'application/json': components['schemas']['dto.CreatePreviewPhotoRequest']
         }
       }
       responses: {
@@ -2012,6 +2071,10 @@ export interface paths {
         }
       }
     }
+    delete?: never
+    options?: never
+    head?: never
+    patch?: never
     trace?: never
   }
   '/api/v1/photographers': {
@@ -2238,7 +2301,7 @@ export interface paths {
             [name: string]: unknown
           }
           content: {
-            'application/json': components['schemas']['dto.HttpResponse-dto_QuotationResponse']
+            'application/json': components['schemas']['dto.HttpResponse-dto_GetQuotationResponse']
           }
         }
         /** @description Bad Request */
@@ -2347,69 +2410,6 @@ export interface paths {
     patch?: never
     trace?: never
   }
-  '/customer/quotations/{id}/review': {
-    parameters: {
-      query?: never
-      header?: never
-      path?: never
-      cookie?: never
-    }
-    get?: never
-    put?: never
-    /**
-     * Create a review
-     * @description Create a review for a quotation.
-     */
-    post: {
-      parameters: {
-        query?: never
-        header?: never
-        path: {
-          /** @description Quotation ID */
-          id: string
-        }
-        cookie?: never
-      }
-      /** @description Review details */
-      requestBody: {
-        content: {
-          'application/json': components['schemas']['dto.CreateReviewRequest']
-        }
-      }
-      responses: {
-        /** @description Review created successfully */
-        204: {
-          headers: {
-            [name: string]: unknown
-          }
-          content?: never
-        }
-        /** @description Bad Request */
-        400: {
-          headers: {
-            [name: string]: unknown
-          }
-          content: {
-            'application/json': components['schemas']['dto.HttpError']
-          }
-        }
-        /** @description Internal Server Error */
-        500: {
-          headers: {
-            [name: string]: unknown
-          }
-          content: {
-            'application/json': components['schemas']['dto.HttpError']
-          }
-        }
-      }
-    }
-    delete?: never
-    options?: never
-    head?: never
-    patch?: never
-    trace?: never
-  }
 }
 export type webhooks = Record<string, never>
 export interface components {
@@ -2448,6 +2448,10 @@ export interface components {
       name: string
       price: number
     }
+    'dto.CreatePreviewPhotoRequest': {
+      link: string
+      quotationId: number
+    }
     'dto.CreateQuotationRequest': {
       customerId: number
       description?: string
@@ -2457,9 +2461,9 @@ export interface components {
       toDate: string
     }
     'dto.CreateReviewRequest': {
-      comment: string
+      comment?: string
       id: string
-      rating?: number
+      rating: number
     }
     'dto.CustomerPublicResponse': {
       id?: number
@@ -2476,6 +2480,18 @@ export interface components {
     'dto.DeleteMediaRequest': {
       mediaID?: number
     }
+    'dto.GetQuotationResponse': {
+      customer?: components['schemas']['dto.UserResponse']
+      description?: string
+      fromDate?: string
+      id?: number
+      package?: components['schemas']['dto.PackageResponse']
+      photographer?: components['schemas']['dto.PhotographerResponse']
+      previews?: components['schemas']['dto.ListPreviewResponse'][]
+      price?: number
+      status?: components['schemas']['model.QuotationStatus']
+      toDate?: string
+    }
     'dto.HttpError': {
       error?: string
     }
@@ -2497,6 +2513,9 @@ export interface components {
     'dto.HttpResponse-dto_CustomerPublicResponse': {
       result?: components['schemas']['dto.CustomerPublicResponse']
     }
+    'dto.HttpResponse-dto_GetQuotationResponse': {
+      result?: components['schemas']['dto.GetQuotationResponse']
+    }
     'dto.HttpResponse-dto_LoginResponse': {
       result?: components['schemas']['dto.LoginResponse']
     }
@@ -2509,9 +2528,6 @@ export interface components {
     'dto.HttpResponse-dto_PhotographerResponse': {
       result?: components['schemas']['dto.PhotographerResponse']
     }
-    'dto.HttpResponse-dto_QuotationResponse': {
-      result?: components['schemas']['dto.QuotationResponse']
-    }
     'dto.HttpResponse-dto_RegisterResponse': {
       result?: components['schemas']['dto.RegisterResponse']
     }
@@ -2521,6 +2537,11 @@ export interface components {
     'dto.HttpResponse-dto_UserResponse': {
       result?: components['schemas']['dto.UserResponse']
     }
+    'dto.ListPreviewResponse': {
+      id?: number
+      link?: string
+      quotationId?: number
+    }
     'dto.LoginRequest': {
       idToken: string
       /** @description GOOGLE */
@@ -2605,7 +2626,7 @@ export interface components {
       package?: components['schemas']['dto.PackageResponse']
       photographer?: components['schemas']['dto.PhotographerResponse']
       price?: number
-      status?: components['schemas']['model.QuotationStatus']
+      status?: string
       toDate?: string
     }
     'dto.ReVerifyCitizenCardRequest': {
diff --git a/src/components/photographer/quotations/photographer-quotation.tsx b/src/components/photographer/quotations/photographer-quotation.tsx
index 21eb1866..1a37c897 100644
--- a/src/components/photographer/quotations/photographer-quotation.tsx
+++ b/src/components/photographer/quotations/photographer-quotation.tsx
@@ -13,11 +13,12 @@ import { Icon } from '@iconify/react'
 import { toast } from 'sonner'
 import z from 'zod'
 
+import { Container } from '@/components/container'
+import QuotationCard from '@/components/quotation/quotation-card'
 import { QuotationDetails } from '@/components/quotation/quotation-details'
 import { Button } from '@/components/ui/button'
 
-import { Container } from '../../container'
-import QuotationCard from '../../quotation/quotation-card'
+import { PreviewView } from './preview-view'
 import CreateQuotationDrawer from './quotation-create-drawer'
 import QuotationForm from './quotation-form'
 import QuotationViewDrawer from './quotation-view-drawer'
@@ -123,17 +124,17 @@ export default function PhotographerQuotation({
       </div>
 
       {quotations.length == 0 && !windowState ? (
-        <div className='text-medium flex h-[69vh] flex-col place-content-center items-center justify-center gap-3 font-medium text-gray-500'>
+        <div className='text-medium flex flex-col place-content-center items-center justify-center gap-3 font-medium text-gray-500'>
           <Icon icon='lucide:sticky-note' className='h-20 w-12 text-gray-500' />
           <span>You don&apos;t have any quotations yet</span>
         </div>
       ) : (
-        <div className='grid h-full grid-cols-1 gap-6 lg:grid-cols-2'>
-          <div className='gap-2.5 space-y-2.5 text-2xl font-bold lg:px-10'>
-            <div>Latest Quotations</div>
+        <div className='grid grid-cols-1 gap-6 lg:grid-cols-2'>
+          <div className='min-h-[400px] gap-2.5 space-y-2 rounded-lg border border-gray-200 bg-white px-4 py-4 text-lg font-bold shadow-md sm:min-h-[500px] sm:px-6 sm:py-5 sm:text-xl md:min-h-[600px] md:text-2xl lg:px-10 lg:py-6 lg:shadow-lg'>
+            <div className='mb-8'>Latest Quotations</div>
 
             {quotations.length == 0 ? (
-              <div className='flex h-full flex-col items-center justify-center gap-3'>
+              <div className='flex flex-col items-center justify-center gap-3'>
                 <Icon icon='lucide:sticky-note' className='size-20' />
                 No Quotations To Show
               </div>
@@ -155,7 +156,6 @@ export default function PhotographerQuotation({
                     />
 
                     {/* Desktop */}
-
                     <QuotationCard
                       quotationId={quotation.quotationID}
                       quotationStatus={quotation.status as QuotationStatus}
@@ -165,6 +165,7 @@ export default function PhotographerQuotation({
                       from={formatDateToString(quotation.from)}
                       to={formatDateToString(quotation.to)}
                       description={quotation.description}
+                      photographerImageUrl={quotation.photographerPictureUrl}
                       duration={calculateDurationFromDate(
                         quotation.from,
                         quotation.to
@@ -182,7 +183,7 @@ export default function PhotographerQuotation({
             )}
           </div>
 
-          <div className='hidden lg:block'>
+          <div className='border-gray-20 hidden gap-2.5 space-y-2.5 rounded-lg border text-2xl lg:block lg:bg-white lg:px-10 lg:py-6 lg:shadow-lg'>
             {windowState === 'create' ? (
               <div className='px-10'>
                 <div className='space-y-4 text-2xl font-bold'>
@@ -202,7 +203,9 @@ export default function PhotographerQuotation({
                 <div className='space-y-4 text-2xl font-bold lg:px-10'>
                   <div className='flex flex-row justify-between'>
                     <div>Quotation {currentQuotation.quotationID}</div>
-                    <Button onClick={onClose}>Close</Button>
+                    <Button onClick={onClose} className='h-8 px-3 text-sm'>
+                      Close
+                    </Button>
                   </div>
 
                   <QuotationForm
@@ -219,10 +222,15 @@ export default function PhotographerQuotation({
                 </div>
               ) : (
                 <div className='flex flex-col gap-4 space-y-4 text-2xl lg:px-10'>
-                  <div className='flex w-full flex-row justify-between font-bold'>
+                  <div className='flex w-full flex-row justify-between text-2xl font-bold'>
                     <div>Quotation {currentQuotation.quotationID}</div>
                     {currentQuotation.status == 'PENDING' && (
-                      <Button onClick={onEditButtonClicked}>Edit</Button>
+                      <Button
+                        onClick={onEditButtonClicked}
+                        className='h-8 px-3 text-sm'
+                      >
+                        Edit
+                      </Button>
                     )}
                   </div>
 
@@ -243,6 +251,13 @@ export default function PhotographerQuotation({
                     )}
                     totalPrice={currentQuotation.price}
                   />
+                  {currentQuotation.status !== 'PENDING' &&
+                    currentQuotation.status !== 'CONFIRMED' && (
+                      <PreviewView
+                        quotationId={currentQuotation.quotationID}
+                        isPhotographer={true}
+                      />
+                    )}
                 </div>
               )
             ) : null}
diff --git a/src/components/photographer/quotations/preview-view.tsx b/src/components/photographer/quotations/preview-view.tsx
new file mode 100644
index 00000000..0a897e3e
--- /dev/null
+++ b/src/components/photographer/quotations/preview-view.tsx
@@ -0,0 +1,124 @@
+import { useEffect, useState } from 'react'
+
+import { createPreview } from '@/actions/quotation/create-preview'
+import { getPreviewById } from '@/actions/quotation/get-preview'
+import { PreviewList } from '@/types/quotation'
+import { Icon } from '@iconify/react'
+import Link from 'next/link'
+import { toast } from 'sonner'
+
+import { Button } from '@/components/ui/button'
+import {
+  Dialog,
+  DialogContent,
+  DialogDescription,
+  DialogFooter,
+  DialogHeader,
+  DialogTitle,
+  DialogTrigger,
+} from '@/components/ui/dialog'
+import { Input } from '@/components/ui/input'
+
+interface PreviewViewProps {
+  quotationId: number
+  isPhotographer: boolean
+}
+
+export function PreviewView({ quotationId, isPhotographer }: PreviewViewProps) {
+  const [albumLink, setAlbumLink] = useState('')
+  const [versions, setVersions] = useState<PreviewList>([])
+  const [showAddAlbumDialog, setShowAddAlbumDialog] = useState(false)
+
+  useEffect(() => {
+    const fetchQuotation = async () => {
+      try {
+        const previews = (await getPreviewById(quotationId)) || []
+        console.log(previews)
+        setVersions(previews || [])
+      } catch (error) {
+        console.error('Error fetching quotation:', error)
+        toast.error('Failed to load quotation previews.')
+      }
+    }
+    fetchQuotation()
+  }, [quotationId])
+
+  const handleAddPreview = () => {
+    try {
+      new URL(albumLink)
+      if (albumLink) {
+        createPreview({ quotationId, link: albumLink })
+        setVersions((prev) => [...(prev || []), { link: albumLink, id: 1 }])
+        setAlbumLink('')
+        setShowAddAlbumDialog(false)
+        toast.success('Album link added successfully!')
+      }
+    } catch {
+      toast.error('Please enter a valid album link.')
+    }
+  }
+
+  return (
+    <div className='mt-5 bg-white p-6'>
+      {/* Add Album Dialog */}
+      {isPhotographer && (
+        <Dialog open={showAddAlbumDialog} onOpenChange={setShowAddAlbumDialog}>
+          <DialogTrigger asChild>
+            <Button
+              variant='secondary'
+              className='mb-6 w-full space-x-2 px-4 py-2'
+            >
+              Add Album
+            </Button>
+          </DialogTrigger>
+
+          <DialogContent className='max-h-[506px] w-11/12 gap-2 rounded-lg p-6'>
+            <DialogHeader>
+              <DialogTitle>Add a New Album</DialogTitle>
+              <DialogDescription>
+                Add external link to link your album.
+              </DialogDescription>
+            </DialogHeader>
+
+            <Input
+              className='mt-2'
+              placeholder='Enter album link...'
+              value={albumLink}
+              onChange={(e) => setAlbumLink(e.target.value)}
+            />
+
+            <DialogFooter className='mt-4 flex justify-end'>
+              <Button onClick={handleAddPreview}>Add</Button>
+            </DialogFooter>
+          </DialogContent>
+        </Dialog>
+      )}
+      {/* File History Section */}
+      <div className='text-xl font-bold'>File History</div>
+      <div className='mt-4 max-h-[150px] overflow-y-auto rounded-sm border-l-4 bg-gray-50 pl-3'>
+        {versions && versions.length > 0 ? (
+          versions.map((version, index) => (
+            <div
+              key={index}
+              className='mb-2 mt-2 flex items-center space-x-3 text-sm text-gray-700'
+            >
+              <Icon icon='bi:file-earmark' className='text-gray-500' />
+              <Link
+                href={version.link || '#'}
+                className='text-black hover:underline'
+                target='_blank'
+                rel='noopener noreferrer'
+              >
+                Version {index + 1}
+              </Link>
+            </div>
+          ))
+        ) : (
+          <p className='mb-2 mt-2 text-sm text-gray-500'>
+            No previews available.
+          </p>
+        )}
+      </div>
+    </div>
+  )
+}
diff --git a/src/components/photographer/quotations/quotation-view-drawer.tsx b/src/components/photographer/quotations/quotation-view-drawer.tsx
index 53d4e407..bd790fea 100644
--- a/src/components/photographer/quotations/quotation-view-drawer.tsx
+++ b/src/components/photographer/quotations/quotation-view-drawer.tsx
@@ -7,6 +7,7 @@ import { formatDateToString } from '@/lib/utils'
 import { QuotationStatus, WindowState } from '@/types/quotation'
 
 import QuotationCard from '@/components/quotation/quotation-card'
+import { QuotationDetails } from '@/components/quotation/quotation-details'
 import { Button } from '@/components/ui/button'
 import {
   Drawer,
@@ -16,8 +17,8 @@ import {
   DrawerTrigger,
 } from '@/components/ui/drawer'
 
-import { QuotationDetails } from '../../quotation/quotation-details'
 import { CreateQuotationForm } from './photographer-quotation'
+import { PreviewView } from './preview-view'
 import QuotationFormDrawer from './quotation-form-drawer'
 
 interface QuotationViewDrawerProps {
@@ -44,6 +45,7 @@ export default function QuotationViewDrawer({
   windowState,
 }: QuotationViewDrawerProps) {
   const [isOpen, setIsOpen] = useState(false)
+  const [showPreview, setShowPreview] = useState(false)
 
   const onClose = () => {
     setWindowstate(null)
@@ -70,6 +72,7 @@ export default function QuotationViewDrawer({
           description={quotation.description}
           duration={calculateDurationFromDate(quotation.from, quotation.to)}
           totalPrice={quotation.price}
+          photographerImageUrl={quotation.photographerPictureUrl}
           onClickEvent={() => {
             setWindowstate(null)
             setCurrentQuotation(quotation)
@@ -118,18 +121,38 @@ export default function QuotationViewDrawer({
             customerId={quotation.customerId.toString()}
           />
         ) : (
-          <QuotationDetails
-            quotationId={quotation.quotationID}
-            quotationStatus={quotation?.status as QuotationStatus}
-            packageName={quotation.packageName}
-            photographerName={quotation.photographerName}
-            customerName={quotation.customerName}
-            from={formatDateToString(quotation.from)}
-            to={formatDateToString(quotation.to)}
-            description={quotation.description}
-            duration={calculateDurationFromDate(quotation.from, quotation.to)}
-            totalPrice={quotation.price}
-          />
+          <div>
+            <QuotationDetails
+              quotationId={quotation.quotationID}
+              quotationStatus={quotation?.status as QuotationStatus}
+              packageName={quotation.packageName}
+              photographerName={quotation.photographerName}
+              customerName={quotation.customerName}
+              from={formatDateToString(quotation.from)}
+              to={formatDateToString(quotation.to)}
+              description={quotation.description}
+              duration={calculateDurationFromDate(quotation.from, quotation.to)}
+              totalPrice={quotation.price}
+            />
+            {quotation.status === 'PAID' ||
+              (quotation.status === 'SUBMITTED' && (
+                <>
+                  {showPreview && (
+                    <PreviewView
+                      quotationId={quotation.quotationID}
+                      isPhotographer={true}
+                    />
+                  )}
+                  {/* View More Button */}
+                  <Button
+                    onClick={() => setShowPreview(!showPreview)}
+                    className='mt-4 text-sm'
+                  >
+                    {showPreview ? 'View Less' : 'View More'}
+                  </Button>
+                </>
+              ))}
+          </div>
         )}
       </DrawerContent>
     </Drawer>
diff --git a/src/components/quotation/index.tsx b/src/components/quotation/index.tsx
index d5d2d359..8e66e697 100644
--- a/src/components/quotation/index.tsx
+++ b/src/components/quotation/index.tsx
@@ -9,9 +9,12 @@ import {
   type CustomerQuotationProps,
   type QuotationStatus,
 } from '@/types/quotation'
+import { Icon } from '@iconify/react'
+import { useRouter } from 'next/navigation'
 import { toast } from 'sonner'
 
 import { Container } from '@/components/container'
+import { PreviewView } from '@/components/photographer/quotations/preview-view'
 import { ProfileHeader } from '@/components/profile-header'
 import { ImageCarousel } from '@/components/quotation/carousel'
 import { QuotationButton } from '@/components/quotation/quotation-button'
@@ -34,6 +37,7 @@ export default function Page({
   quotationImages,
 }: CustomerQuotationProps) {
   const [status, setStatus] = useState<QuotationStatus>(quotationStatus)
+  const router = useRouter()
   const [ratingScore, setRatingScore] = useState<number>(0.0)
   const [comment, setComment] = useState<string>()
 
@@ -88,6 +92,15 @@ export default function Page({
         </div>
 
         <div className='flex-1'>
+          {/* Go Back Button */}
+          <div className='mb-2 flex size-10 cursor-pointer items-center justify-center rounded-full p-2 hover:bg-gray-200'>
+            <Icon
+              icon='lucide:chevron-left'
+              className='text-xl'
+              onClick={() => router.back()}
+            />
+          </div>
+
           <QuotationDetails
             quotationId={quotationId}
             quotationStatus={status as QuotationStatus}
@@ -100,13 +113,21 @@ export default function Page({
             duration={duration}
             totalPrice={totalPrice}
           />
+          {status === 'PAID' ||
+            (status === 'SUBMITTED' && (
+              <>
+                <PreviewView quotationId={quotationId} isPhotographer={false} />
+              </>
+            ))}
           <QuotationButton
             status={status}
             onCancel={handleCancel}
             onConfirm={handleConfirm}
             onPay={handlePayment}
           />
-          {(quotationStatus === 'PAID' || quotationStatus === 'CANCELLED') && (
+          {(status === 'PAID' ||
+            status === 'SUBMITTED' ||
+            status === 'CANCELLED') && (
             <QuotationComment
               ratingScore={ratingScore}
               handleStarOnChange={setRatingScore}
diff --git a/src/components/quotation/quotation-details.tsx b/src/components/quotation/quotation-details.tsx
index 419745d9..6b01527d 100644
--- a/src/components/quotation/quotation-details.tsx
+++ b/src/components/quotation/quotation-details.tsx
@@ -10,6 +10,7 @@ const statusColor: { [key: string]: string } = {
   CONFIRMED: 'text-sky-600',
   PAID: 'text-emerald-600',
   CANCELLED: 'text-red-600',
+  SUBMITTED: 'text-emerald-600',
 }
 
 export function QuotationDetails({
@@ -42,7 +43,7 @@ export function QuotationDetails({
   ]
 
   return (
-    <div className='flex justify-center px-4'>
+    <div className='flex flex-col justify-center px-4'>
       <div className='flex w-full flex-col'>
         {detailItems.map((item, i) => (
           <React.Fragment key={i}>
diff --git a/src/components/quotation/quotation-list.tsx b/src/components/quotation/quotation-list.tsx
index 5012e0e8..dbc6d22b 100644
--- a/src/components/quotation/quotation-list.tsx
+++ b/src/components/quotation/quotation-list.tsx
@@ -28,6 +28,7 @@ export function QuotationList({ quotations }: QuotationListProps) {
               photographerName={quotation.photographerName}
               customerName={quotation.customerName}
               from={formatDateToString(quotation.from)}
+              photographerImageUrl={quotation.photographerPictureUrl}
               to={formatDateToString(quotation.to)}
               description={quotation.description}
               duration={calculateDurationFromDate(quotation.from, quotation.to)}
diff --git a/src/components/quotation/quotation-status-tag.tsx b/src/components/quotation/quotation-status-tag.tsx
index 5012cdbb..c6d5fcad 100644
--- a/src/components/quotation/quotation-status-tag.tsx
+++ b/src/components/quotation/quotation-status-tag.tsx
@@ -15,6 +15,7 @@ const statusVariants = cva(
         CONFIRMED: 'bg-sky-100 text-sky-700',
         CANCELLED: 'bg-red-100 text-red-700',
         PAID: 'bg-emerald-100 text-emerald-700',
+        SUBMITTED: 'bg-emerald-100 text-emerald-700',
       },
       size: {
         default: 'px-2 py-1',
diff --git a/src/types/quotation.ts b/src/types/quotation.ts
index 85fe7f2a..08207806 100644
--- a/src/types/quotation.ts
+++ b/src/types/quotation.ts
@@ -1,4 +1,13 @@
-export type QuotationStatus = 'PENDING' | 'CONFIRMED' | 'PAID' | 'CANCELLED'
+import { components } from '@/api/schema'
+
+export type QuotationStatus =
+  | 'PENDING'
+  | 'CONFIRMED'
+  | 'PAID'
+  | 'CANCELLED'
+  | 'SUBMITTED'
+
+export type Quotation = components['schemas']['dto.QuotationResponse']
 
 export interface QuotationDetailsProps {
   quotationId: number
@@ -20,3 +29,6 @@ export interface CustomerQuotationProps extends QuotationDetailsProps {
 }
 
 export type WindowState = 'create' | 'edit' | null
+
+export type PreviewList =
+  components['schemas']['dto.GetQuotationResponse']['previews']


Reviews:
user: login: patthapolkit
body: Minor request changes
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T06:11:57Z
user: login: patthapolkit
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T08:29:05Z

Review comments:
diff_hunk: @@ -118,18 +121,38 @@ export default function QuotationViewDrawer({
             customerId={quotation.customerId.toString()}
           />
         ) : (
-          <QuotationDetails
-            quotationId={quotation.quotationID}
-            quotationStatus={quotation?.status as QuotationStatus}
-            packageName={quotation.packageName}
-            photographerName={quotation.photographerName}
-            customerName={quotation.customerName}
-            from={formatDateToString(quotation.from)}
-            to={formatDateToString(quotation.to)}
-            description={quotation.description}
-            duration={calculateDurationFromDate(quotation.from, quotation.to)}
-            totalPrice={quotation.price}
-          />
+          <div>
+            <QuotationDetails
+              quotationId={quotation.quotationID}
+              quotationStatus={quotation?.status as QuotationStatus}
+              packageName={quotation.packageName}
+              photographerName={quotation.photographerName}
+              customerName={quotation.customerName}
+              from={formatDateToString(quotation.from)}
+              to={formatDateToString(quotation.to)}
+              description={quotation.description}
+              duration={calculateDurationFromDate(quotation.from, quotation.to)}
+              totalPrice={quotation.price}
+            />
+            {quotation.status !== 'PENDING' &&
+              quotation.status !== 'CONFIRMED' && (
path: src/components/photographer/quotations/quotation-view-drawer.tsx
user: login: patthapolkit
body: quotation status CANCELLED shouldn't show preview view also
created_at: 2025-03-25T06:05:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -18,6 +20,8 @@ import { QuotationButton } from '@/components/quotation/quotation-button'
 import { QuotationComment } from '@/components/quotation/quotation-comment'
 import { QuotationDetails } from '@/components/quotation/quotation-details'
 
+import { PreviewView } from '../photographer/quotations/preview-view'
path: src/components/quotation/index.tsx
user: login: patthapolkit
body: use absolute path
created_at: 2025-03-25T06:06:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -182,7 +183,7 @@ export default function PhotographerQuotation({
             )}
           </div>
 
-          <div className='hidden lg:block'>
+          <div className='border-gray-20 hidden gap-2.5 space-y-2.5 rounded-lg border text-2xl font-bold lg:block lg:bg-white lg:px-10 lg:py-6 lg:shadow-lg'>
path: src/components/photographer/quotations/photographer-quotation.tsx
user: login: patthapolkit
body: remove font bold
created_at: 2025-03-25T06:11:42Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-25T06:31:02Z
body: Preview deployment is live at 
🔗https://pic-me-pls-frontend-594eaoh9u-chanatpakornds-projects.vercel.app
author_association: NONE